home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-9.10-netbook-remix-PL.iso / casper / filesystem.squashfs / usr / lib / pymodules / python2.6 / feedparser.pyc (.txt) < prev    next >
Encoding:
Python Compiled Bytecode  |  2009-11-02  |  97.6 KB  |  3,096 lines

  1. # Source Generated with Decompyle++
  2. # File: in.pyc (Python 2.6)
  3.  
  4. '''Universal feed parser
  5.  
  6. Handles RSS 0.9x, RSS 1.0, RSS 2.0, CDF, Atom 0.3, and Atom 1.0 feeds
  7.  
  8. Visit http://feedparser.org/ for the latest version
  9. Visit http://feedparser.org/docs/ for the latest documentation
  10.  
  11. Required: Python 2.1 or later
  12. Recommended: Python 2.3 or later
  13. Recommended: CJKCodecs and iconv_codec <http://cjkpython.i18n.org/>
  14. '''
  15. __version__ = '4.1'
  16. __license__ = "Copyright (c) 2002-2006, Mark Pilgrim, All rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice,\n  this list of conditions and the following disclaimer.\n* Redistributions in binary form must reproduce the above copyright notice,\n  this list of conditions and the following disclaimer in the documentation\n  and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS'\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\nARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE\nLIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\nCONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\nSUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\nINTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN\nCONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\nARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGE."
  17. __author__ = 'Mark Pilgrim <http://diveintomark.org/>'
  18. __contributors__ = [
  19.     'Jason Diamond <http://injektilo.org/>',
  20.     'John Beimler <http://john.beimler.org/>',
  21.     'Fazal Majid <http://www.majid.info/mylos/weblog/>',
  22.     'Aaron Swartz <http://aaronsw.com/>',
  23.     'Kevin Marks <http://epeus.blogspot.com/>']
  24. _debug = 0
  25. USER_AGENT = 'UniversalFeedParser/%s +http://feedparser.org/' % __version__
  26. ACCEPT_HEADER = 'application/atom+xml,application/rdf+xml,application/rss+xml,application/x-netcdf,application/xml;q=0.9,text/xml;q=0.2,*/*;q=0.1'
  27. PREFERRED_XML_PARSERS = [
  28.     'drv_libxml2']
  29. TIDY_MARKUP = 0
  30. PREFERRED_TIDY_INTERFACES = [
  31.     'uTidy',
  32.     'mxTidy']
  33. import sgmllib
  34. import re
  35. import sys
  36. import copy
  37. import urlparse
  38. import time
  39. import rfc822
  40. import types
  41. import cgi
  42. import urllib
  43. import urllib2
  44.  
  45. try:
  46.     from cStringIO import StringIO as _StringIO
  47. except:
  48.     from StringIO import StringIO as _StringIO
  49.  
  50.  
  51. try:
  52.     import gzip
  53. except:
  54.     gzip = None
  55.  
  56.  
  57. try:
  58.     import zlib
  59. except:
  60.     zlib = None
  61.  
  62.  
  63. try:
  64.     import xml.sax as xml
  65.     xml.sax.make_parser(PREFERRED_XML_PARSERS)
  66.     from xml.sax.saxutils import escape as _xmlescape
  67.     _XML_AVAILABLE = 1
  68. except:
  69.     _XML_AVAILABLE = 0
  70.     
  71.     def _xmlescape(data):
  72.         data = data.replace('&', '&')
  73.         data = data.replace('>', '>')
  74.         data = data.replace('<', '<')
  75.         return data
  76.  
  77.  
  78.  
  79. try:
  80.     import base64
  81.     import binascii
  82. except:
  83.     base64 = binascii = None
  84.  
  85.  
  86. try:
  87.     import cjkcodecs.aliases as cjkcodecs
  88. except:
  89.     pass
  90.  
  91.  
  92. try:
  93.     import iconv_codec
  94. except:
  95.     pass
  96.  
  97.  
  98. try:
  99.     import chardet
  100.     if _debug:
  101.         import chardet.constants as chardet
  102.         chardet.constants._debug = 1
  103. except:
  104.     chardet = None
  105.  
  106.  
  107. class ThingsNobodyCaresAboutButMe(Exception):
  108.     pass
  109.  
  110.  
  111. class CharacterEncodingOverride(ThingsNobodyCaresAboutButMe):
  112.     pass
  113.  
  114.  
  115. class CharacterEncodingUnknown(ThingsNobodyCaresAboutButMe):
  116.     pass
  117.  
  118.  
  119. class NonXMLContentType(ThingsNobodyCaresAboutButMe):
  120.     pass
  121.  
  122.  
  123. class UndeclaredNamespace(Exception):
  124.     pass
  125.  
  126. sgmllib.tagfind = re.compile('[a-zA-Z][-_.:a-zA-Z0-9]*')
  127. sgmllib.special = re.compile('<!')
  128. sgmllib.charref = re.compile('&#(x?[0-9A-Fa-f]+)[^0-9A-Fa-f]')
  129. SUPPORTED_VERSIONS = {
  130.     '': 'unknown',
  131.     'rss090': 'RSS 0.90',
  132.     'rss091n': 'RSS 0.91 (Netscape)',
  133.     'rss091u': 'RSS 0.91 (Userland)',
  134.     'rss092': 'RSS 0.92',
  135.     'rss093': 'RSS 0.93',
  136.     'rss094': 'RSS 0.94',
  137.     'rss20': 'RSS 2.0',
  138.     'rss10': 'RSS 1.0',
  139.     'rss': 'RSS (unknown version)',
  140.     'atom01': 'Atom 0.1',
  141.     'atom02': 'Atom 0.2',
  142.     'atom03': 'Atom 0.3',
  143.     'atom10': 'Atom 1.0',
  144.     'atom': 'Atom (unknown version)',
  145.     'cdf': 'CDF',
  146.     'hotrss': 'Hot RSS' }
  147.  
  148. try:
  149.     UserDict = dict
  150. except NameError:
  151.     from UserDict import UserDict
  152.     
  153.     def dict(aList):
  154.         rc = { }
  155.         for k, v in aList:
  156.             rc[k] = v
  157.         
  158.         return rc
  159.  
  160.  
  161.  
  162. class FeedParserDict(UserDict):
  163.     keymap = {
  164.         'channel': 'feed',
  165.         'items': 'entries',
  166.         'guid': 'id',
  167.         'date': 'updated',
  168.         'date_parsed': 'updated_parsed',
  169.         'description': [
  170.             'subtitle',
  171.             'summary'],
  172.         'url': [
  173.             'href'],
  174.         'modified': 'updated',
  175.         'modified_parsed': 'updated_parsed',
  176.         'issued': 'published',
  177.         'issued_parsed': 'published_parsed',
  178.         'copyright': 'rights',
  179.         'copyright_detail': 'rights_detail',
  180.         'tagline': 'subtitle',
  181.         'tagline_detail': 'subtitle_detail' }
  182.     
  183.     def __getitem__(self, key):
  184.         if key == 'category':
  185.             return UserDict.__getitem__(self, 'tags')[0]['term']
  186.         if key == 'categories':
  187.             return [ (tag['scheme'], tag['term']) for tag in UserDict.__getitem__(self, 'tags') ]
  188.         realkey = self.keymap.get(key, key)
  189.         if UserDict.has_key(self, key):
  190.             return UserDict.__getitem__(self, key)
  191.         return UserDict.__getitem__(self, realkey)
  192.  
  193.     
  194.     def __setitem__(self, key, value):
  195.         for k in self.keymap.keys():
  196.             if key == k:
  197.                 key = self.keymap[k]
  198.                 if type(key) == types.ListType:
  199.                     key = key[0]
  200.                 
  201.             type(key) == types.ListType
  202.         
  203.         return UserDict.__setitem__(self, key, value)
  204.  
  205.     
  206.     def get(self, key, default = None):
  207.         if self.has_key(key):
  208.             return self[key]
  209.         return default
  210.  
  211.     
  212.     def setdefault(self, key, value):
  213.         if not self.has_key(key):
  214.             self[key] = value
  215.         
  216.         return self[key]
  217.  
  218.     
  219.     def has_key(self, key):
  220.         
  221.         try:
  222.             if not hasattr(self, key):
  223.                 pass
  224.             return UserDict.has_key(self, key)
  225.         except AttributeError:
  226.             return False
  227.  
  228.  
  229.     
  230.     def __getattr__(self, key):
  231.         
  232.         try:
  233.             return self.__dict__[key]
  234.         except KeyError:
  235.             pass
  236.  
  237.         
  238.         try:
  239.             if not not key.startswith('_'):
  240.                 raise AssertionError
  241.             return self.__getitem__(key)
  242.         except:
  243.             raise AttributeError, "object has no attribute '%s'" % key
  244.  
  245.  
  246.     
  247.     def __setattr__(self, key, value):
  248.         if key.startswith('_') or key == 'data':
  249.             self.__dict__[key] = value
  250.         else:
  251.             return self.__setitem__(key, value)
  252.         return key == 'data'
  253.  
  254.     
  255.     def __contains__(self, key):
  256.         return self.has_key(key)
  257.  
  258.  
  259.  
  260. def zopeCompatibilityHack():
  261.     global FeedParserDict, FeedParserDict
  262.     del FeedParserDict
  263.     
  264.     def FeedParserDict(aDict = None):
  265.         rc = { }
  266.         if aDict:
  267.             rc.update(aDict)
  268.         
  269.         return rc
  270.  
  271.  
  272. _ebcdic_to_ascii_map = None
  273.  
  274. def _ebcdic_to_ascii(s):
  275.     global _ebcdic_to_ascii_map
  276.     if not _ebcdic_to_ascii_map:
  277.         emap = (0, 1, 2, 3, 156, 9, 134, 127, 151, 141, 142, 11, 12, 13, 14, 15, 16, 17, 18, 19, 157, 133, 8, 135, 24, 25, 146, 143, 28, 29, 30, 31, 128, 129, 130, 131, 132, 10, 23, 27, 136, 137, 138, 139, 140, 5, 6, 7, 144, 145, 22, 147, 148, 149, 150, 4, 152, 153, 154, 155, 20, 21, 158, 26, 32, 160, 161, 162, 163, 164, 165, 166, 167, 168, 91, 46, 60, 40, 43, 33, 38, 169, 170, 171, 172, 173, 174, 175, 176, 177, 93, 36, 42, 41, 59, 94, 45, 47, 178, 179, 180, 181, 182, 183, 184, 185, 124, 44, 37, 95, 62, 63, 186, 187, 188, 189, 190, 191, 192, 193, 194, 96, 58, 35, 64, 39, 61, 34, 195, 97, 98, 99, 100, 101, 102, 103, 104, 105, 196, 197, 198, 199, 200, 201, 202, 106, 107, 108, 109, 110, 111, 112, 113, 114, 203, 204, 205, 206, 207, 208, 209, 126, 115, 116, 117, 118, 119, 120, 121, 122, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 123, 65, 66, 67, 68, 69, 70, 71, 72, 73, 232, 233, 234, 235, 236, 237, 125, 74, 75, 76, 77, 78, 79, 80, 81, 82, 238, 239, 240, 241, 242, 243, 92, 159, 83, 84, 85, 86, 87, 88, 89, 90, 244, 245, 246, 247, 248, 249, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 250, 251, 252, 253, 254, 255)
  278.         import string
  279.         _ebcdic_to_ascii_map = string.maketrans(''.join(map(chr, range(256))), ''.join(map(chr, emap)))
  280.     
  281.     return s.translate(_ebcdic_to_ascii_map)
  282.  
  283. _urifixer = re.compile('^([A-Za-z][A-Za-z0-9+-.]*://)(/*)(.*?)')
  284.  
  285. def _urljoin(base, uri):
  286.     uri = _urifixer.sub('\\1\\3', uri)
  287.     return urlparse.urljoin(base, uri)
  288.  
  289.  
  290. class _FeedParserMixin:
  291.     namespaces = {
  292.         '': '',
  293.         'http://backend.userland.com/rss': '',
  294.         'http://blogs.law.harvard.edu/tech/rss': '',
  295.         'http://purl.org/rss/1.0/': '',
  296.         'http://my.netscape.com/rdf/simple/0.9/': '',
  297.         'http://example.com/newformat#': '',
  298.         'http://example.com/necho': '',
  299.         'http://purl.org/echo/': '',
  300.         'uri/of/echo/namespace#': '',
  301.         'http://purl.org/pie/': '',
  302.         'http://purl.org/atom/ns#': '',
  303.         'http://www.w3.org/2005/Atom': '',
  304.         'http://purl.org/rss/1.0/modules/rss091#': '',
  305.         'http://webns.net/mvcb/': 'admin',
  306.         'http://purl.org/rss/1.0/modules/aggregation/': 'ag',
  307.         'http://purl.org/rss/1.0/modules/annotate/': 'annotate',
  308.         'http://media.tangent.org/rss/1.0/': 'audio',
  309.         'http://backend.userland.com/blogChannelModule': 'blogChannel',
  310.         'http://web.resource.org/cc/': 'cc',
  311.         'http://backend.userland.com/creativeCommonsRssModule': 'creativeCommons',
  312.         'http://purl.org/rss/1.0/modules/company': 'co',
  313.         'http://purl.org/rss/1.0/modules/content/': 'content',
  314.         'http://my.theinfo.org/changed/1.0/rss/': 'cp',
  315.         'http://purl.org/dc/elements/1.1/': 'dc',
  316.         'http://purl.org/dc/terms/': 'dcterms',
  317.         'http://purl.org/rss/1.0/modules/email/': 'email',
  318.         'http://purl.org/rss/1.0/modules/event/': 'ev',
  319.         'http://rssnamespace.org/feedburner/ext/1.0': 'feedburner',
  320.         'http://freshmeat.net/rss/fm/': 'fm',
  321.         'http://xmlns.com/foaf/0.1/': 'foaf',
  322.         'http://www.w3.org/2003/01/geo/wgs84_pos#': 'geo',
  323.         'http://postneo.com/icbm/': 'icbm',
  324.         'http://purl.org/rss/1.0/modules/image/': 'image',
  325.         'http://www.itunes.com/DTDs/PodCast-1.0.dtd': 'itunes',
  326.         'http://example.com/DTDs/PodCast-1.0.dtd': 'itunes',
  327.         'http://purl.org/rss/1.0/modules/link/': 'l',
  328.         'http://search.yahoo.com/mrss': 'media',
  329.         'http://madskills.com/public/xml/rss/module/pingback/': 'pingback',
  330.         'http://prismstandard.org/namespaces/1.2/basic/': 'prism',
  331.         'http://www.w3.org/1999/02/22-rdf-syntax-ns#': 'rdf',
  332.         'http://www.w3.org/2000/01/rdf-schema#': 'rdfs',
  333.         'http://purl.org/rss/1.0/modules/reference/': 'ref',
  334.         'http://purl.org/rss/1.0/modules/richequiv/': 'reqv',
  335.         'http://purl.org/rss/1.0/modules/search/': 'search',
  336.         'http://purl.org/rss/1.0/modules/slash/': 'slash',
  337.         'http://schemas.xmlsoap.org/soap/envelope/': 'soap',
  338.         'http://purl.org/rss/1.0/modules/servicestatus/': 'ss',
  339.         'http://hacks.benhammersley.com/rss/streaming/': 'str',
  340.         'http://purl.org/rss/1.0/modules/subscription/': 'sub',
  341.         'http://purl.org/rss/1.0/modules/syndication/': 'sy',
  342.         'http://purl.org/rss/1.0/modules/taxonomy/': 'taxo',
  343.         'http://purl.org/rss/1.0/modules/threading/': 'thr',
  344.         'http://purl.org/rss/1.0/modules/textinput/': 'ti',
  345.         'http://madskills.com/public/xml/rss/module/trackback/': 'trackback',
  346.         'http://wellformedweb.org/commentAPI/': 'wfw',
  347.         'http://purl.org/rss/1.0/modules/wiki/': 'wiki',
  348.         'http://www.w3.org/1999/xhtml': 'xhtml',
  349.         'http://www.w3.org/XML/1998/namespace': 'xml',
  350.         'http://schemas.pocketsoap.com/rss/myDescModule/': 'szf' }
  351.     _matchnamespaces = { }
  352.     can_be_relative_uri = [
  353.         'link',
  354.         'id',
  355.         'wfw_comment',
  356.         'wfw_commentrss',
  357.         'docs',
  358.         'url',
  359.         'href',
  360.         'comments',
  361.         'license',
  362.         'icon',
  363.         'logo']
  364.     can_contain_relative_uris = [
  365.         'content',
  366.         'title',
  367.         'summary',
  368.         'info',
  369.         'tagline',
  370.         'subtitle',
  371.         'copyright',
  372.         'rights',
  373.         'description']
  374.     can_contain_dangerous_markup = [
  375.         'content',
  376.         'title',
  377.         'summary',
  378.         'info',
  379.         'tagline',
  380.         'subtitle',
  381.         'copyright',
  382.         'rights',
  383.         'description']
  384.     html_types = [
  385.         'text/html',
  386.         'application/xhtml+xml']
  387.     
  388.     def __init__(self, baseuri = None, baselang = None, encoding = 'utf-8'):
  389.         if _debug:
  390.             sys.stderr.write('initializing FeedParser\n')
  391.         
  392.         if not self._matchnamespaces:
  393.             for k, v in self.namespaces.items():
  394.                 self._matchnamespaces[k.lower()] = v
  395.             
  396.         
  397.         self.feeddata = FeedParserDict()
  398.         self.encoding = encoding
  399.         self.entries = []
  400.         self.version = ''
  401.         self.namespacesInUse = { }
  402.         self.infeed = 0
  403.         self.inentry = 0
  404.         self.incontent = 0
  405.         self.intextinput = 0
  406.         self.inimage = 0
  407.         self.inauthor = 0
  408.         self.incontributor = 0
  409.         self.inpublisher = 0
  410.         self.insource = 0
  411.         self.sourcedata = FeedParserDict()
  412.         self.contentparams = FeedParserDict()
  413.         self._summaryKey = None
  414.         self.namespacemap = { }
  415.         self.elementstack = []
  416.         self.basestack = []
  417.         self.langstack = []
  418.         if not baseuri:
  419.             pass
  420.         self.baseuri = ''
  421.         if not baselang:
  422.             pass
  423.         self.lang = None
  424.         if baselang:
  425.             self.feeddata['language'] = baselang
  426.         
  427.  
  428.     
  429.     def unknown_starttag(self, tag, attrs):
  430.         if _debug:
  431.             sys.stderr.write('start %s with %s\n' % (tag, attrs))
  432.         
  433.         attrs = [ (k.lower(), v) for k, v in attrs ]
  434.         attrs = [ (k, v) for k, v in attrs ]
  435.         attrsD = dict(attrs)
  436.         if not attrsD.get('xml:base', attrsD.get('base')):
  437.             pass
  438.         baseuri = self.baseuri
  439.         self.baseuri = _urljoin(self.baseuri, baseuri)
  440.         lang = attrsD.get('xml:lang', attrsD.get('lang'))
  441.         if lang == '':
  442.             lang = None
  443.         elif lang is None:
  444.             lang = self.lang
  445.         
  446.         self.lang = lang
  447.         self.basestack.append(self.baseuri)
  448.         self.langstack.append(lang)
  449.         for prefix, uri in attrs:
  450.             if prefix.startswith('xmlns:'):
  451.                 self.trackNamespace(prefix[6:], uri)
  452.                 continue
  453.             None if lang else []
  454.             if prefix == 'xmlns':
  455.                 self.trackNamespace(None, uri)
  456.                 continue
  457.         
  458.         if self.incontent and self.contentparams.has_key('type') and not self.contentparams.get('type', 'xml').endswith('xml'):
  459.             self.contentparams['type'] = 'application/xhtml+xml'
  460.         
  461.         if self.incontent and self.contentparams.get('type') == 'application/xhtml+xml':
  462.             tag = tag.split(':')[-1]
  463.             return tag(''.join % ([], []([ ' %s="%s"' % t for t in attrs ])), escape = 0)
  464.         if tag.find(':') != -1:
  465.             (prefix, suffix) = tag.split(':', 1)
  466.         else:
  467.             prefix = ''
  468.             suffix = tag
  469.         prefix = self.namespacemap.get(prefix, prefix)
  470.         if prefix:
  471.             prefix = prefix + '_'
  472.         
  473.         if not prefix and tag not in ('title', 'link', 'description', 'name'):
  474.             self.intextinput = 0
  475.         
  476.         if not prefix and tag not in ('title', 'link', 'description', 'url', 'href', 'width', 'height'):
  477.             self.inimage = 0
  478.         
  479.         methodname = '_start_' + prefix + suffix
  480.         
  481.         try:
  482.             method = getattr(self, methodname)
  483.             return method(attrsD)
  484.         except AttributeError:
  485.             return self.push(prefix + suffix, 1)
  486.  
  487.  
  488.     
  489.     def unknown_endtag(self, tag):
  490.         if _debug:
  491.             sys.stderr.write('end %s\n' % tag)
  492.         
  493.         if tag.find(':') != -1:
  494.             (prefix, suffix) = tag.split(':', 1)
  495.         else:
  496.             prefix = ''
  497.             suffix = tag
  498.         prefix = self.namespacemap.get(prefix, prefix)
  499.         if prefix:
  500.             prefix = prefix + '_'
  501.         
  502.         methodname = '_end_' + prefix + suffix
  503.         
  504.         try:
  505.             method = getattr(self, methodname)
  506.             method()
  507.         except AttributeError:
  508.             self.pop(prefix + suffix)
  509.  
  510.         if self.incontent and self.contentparams.has_key('type') and not self.contentparams.get('type', 'xml').endswith('xml'):
  511.             self.contentparams['type'] = 'application/xhtml+xml'
  512.         
  513.         if self.incontent and self.contentparams.get('type') == 'application/xhtml+xml':
  514.             tag = tag.split(':')[-1]
  515.             self.handle_data('</%s>' % tag, escape = 0)
  516.         
  517.         if self.basestack:
  518.             self.basestack.pop()
  519.             if self.basestack and self.basestack[-1]:
  520.                 self.baseuri = self.basestack[-1]
  521.             
  522.         
  523.         if self.langstack:
  524.             self.langstack.pop()
  525.             if self.langstack:
  526.                 self.lang = self.langstack[-1]
  527.             
  528.         
  529.  
  530.     
  531.     def handle_charref(self, ref):
  532.         if not self.elementstack:
  533.             return None
  534.         ref = ref.lower()
  535.         if ref in ('34', '38', '39', '60', '62', 'x22', 'x26', 'x27', 'x3c', 'x3e'):
  536.             text = '&#%s;' % ref
  537.         elif ref[0] == 'x':
  538.             c = int(ref[1:], 16)
  539.         else:
  540.             c = int(ref)
  541.         text = unichr(c).encode('utf-8')
  542.         self.elementstack[-1][2].append(text)
  543.  
  544.     
  545.     def handle_entityref(self, ref):
  546.         if not self.elementstack:
  547.             return None
  548.         if _debug:
  549.             sys.stderr.write('entering handle_entityref with %s\n' % ref)
  550.         
  551.         if ref in ('lt', 'gt', 'quot', 'amp', 'apos'):
  552.             text = '&%s;' % ref
  553.         else:
  554.             
  555.             def name2cp(k):
  556.                 import htmlentitydefs
  557.                 if hasattr(htmlentitydefs, 'name2codepoint'):
  558.                     return htmlentitydefs.name2codepoint[k]
  559.                 k = htmlentitydefs.entitydefs[k]
  560.                 if k.startswith('&#') and k.endswith(';'):
  561.                     return int(k[2:-1])
  562.                 return ord(k)
  563.  
  564.             
  565.             try:
  566.                 name2cp(ref)
  567.             except KeyError:
  568.                 text = '&%s;' % ref
  569.  
  570.             text = unichr(name2cp(ref)).encode('utf-8')
  571.         self.elementstack[-1][2].append(text)
  572.  
  573.     
  574.     def handle_data(self, text, escape = 1):
  575.         if not self.elementstack:
  576.             return None
  577.         if escape and self.contentparams.get('type') == 'application/xhtml+xml':
  578.             text = _xmlescape(text)
  579.         
  580.         self.elementstack[-1][2].append(text)
  581.  
  582.     
  583.     def handle_comment(self, text):
  584.         pass
  585.  
  586.     
  587.     def handle_pi(self, text):
  588.         pass
  589.  
  590.     
  591.     def handle_decl(self, text):
  592.         pass
  593.  
  594.     
  595.     def parse_declaration(self, i):
  596.         if _debug:
  597.             sys.stderr.write('entering parse_declaration\n')
  598.         
  599.         if self.rawdata[i:i + 9] == '<![CDATA[':
  600.             k = self.rawdata.find(']]>', i)
  601.             if k == -1:
  602.                 k = len(self.rawdata)
  603.             
  604.             self.handle_data(_xmlescape(self.rawdata[i + 9:k]), 0)
  605.             return k + 3
  606.         k = self.rawdata.find('>', i)
  607.         return k + 1
  608.  
  609.     
  610.     def mapContentType(self, contentType):
  611.         contentType = contentType.lower()
  612.         if contentType == 'text' or contentType == 'plain':
  613.             contentType = 'text/plain'
  614.         elif contentType == 'html':
  615.             contentType = 'text/html'
  616.         elif contentType == 'xhtml':
  617.             contentType = 'application/xhtml+xml'
  618.         
  619.         return contentType
  620.  
  621.     
  622.     def trackNamespace(self, prefix, uri):
  623.         loweruri = uri.lower()
  624.         if (prefix, loweruri) == (None, 'http://my.netscape.com/rdf/simple/0.9/') and not (self.version):
  625.             self.version = 'rss090'
  626.         
  627.         if loweruri == 'http://purl.org/rss/1.0/' and not (self.version):
  628.             self.version = 'rss10'
  629.         
  630.         if loweruri == 'http://www.w3.org/2005/atom' and not (self.version):
  631.             self.version = 'atom10'
  632.         
  633.         if loweruri.find('backend.userland.com/rss') != -1:
  634.             uri = 'http://backend.userland.com/rss'
  635.             loweruri = uri
  636.         
  637.         if self._matchnamespaces.has_key(loweruri):
  638.             self.namespacemap[prefix] = self._matchnamespaces[loweruri]
  639.             self.namespacesInUse[self._matchnamespaces[loweruri]] = uri
  640.         elif not prefix:
  641.             pass
  642.         self.namespacesInUse[''] = uri
  643.  
  644.     
  645.     def resolveURI(self, uri):
  646.         if not self.baseuri:
  647.             pass
  648.         return _urljoin('', uri)
  649.  
  650.     
  651.     def decodeEntities(self, element, data):
  652.         return data
  653.  
  654.     
  655.     def push(self, element, expectingText):
  656.         self.elementstack.append([
  657.             element,
  658.             expectingText,
  659.             []])
  660.  
  661.     
  662.     def pop(self, element, stripWhitespace = 1):
  663.         if not self.elementstack:
  664.             return None
  665.         if self.elementstack[-1][0] != element:
  666.             return None
  667.         (element, expectingText, pieces) = self.elementstack.pop()
  668.         output = ''.join(pieces)
  669.         if not expectingText:
  670.             return output
  671.         if base64 and self.contentparams.get('base64', 0):
  672.             
  673.             try:
  674.                 output = base64.decodestring(output)
  675.             except binascii.Error:
  676.                 expectingText
  677.                 expectingText
  678.                 None if stripWhitespace else self.elementstack
  679.             except binascii.Incomplete:
  680.                 expectingText
  681.                 expectingText
  682.                 expectingText
  683.             except:
  684.                 expectingText<EXCEPTION MATCH>binascii.Error
  685.             
  686.  
  687.         expectingText
  688.         if not self.contentparams.get('base64', 0):
  689.             output = self.decodeEntities(element, output)
  690.         
  691.         
  692.         try:
  693.             del self.contentparams['mode']
  694.         except KeyError:
  695.             pass
  696.  
  697.         
  698.         try:
  699.             del self.contentparams['base64']
  700.         except KeyError:
  701.             pass
  702.  
  703.         if self.mapContentType(self.contentparams.get('type', 'text/html')) in self.html_types:
  704.             if element in self.can_contain_relative_uris:
  705.                 output = _resolveRelativeURIs(output, self.baseuri, self.encoding)
  706.             
  707.         
  708.         if self.mapContentType(self.contentparams.get('type', 'text/html')) in self.html_types:
  709.             if element in self.can_contain_dangerous_markup:
  710.                 output = _sanitizeHTML(output, self.encoding)
  711.             
  712.         
  713.         if self.encoding and type(output) != type(u''):
  714.             
  715.             try:
  716.                 output = unicode(output, self.encoding)
  717.  
  718.         
  719.         if element == 'category':
  720.             return output
  721.         if self.inentry and not (self.insource):
  722.             if element == 'content':
  723.                 self.entries[-1].setdefault(element, [])
  724.                 contentparams = copy.deepcopy(self.contentparams)
  725.                 contentparams['value'] = output
  726.                 self.entries[-1][element].append(contentparams)
  727.             elif element == 'link':
  728.                 self.entries[-1][element] = output
  729.                 if output:
  730.                     self.entries[-1]['links'][-1]['href'] = output
  731.                 
  732.             elif element == 'description':
  733.                 element = 'summary'
  734.             
  735.             self.entries[-1][element] = output
  736.             if self.incontent:
  737.                 contentparams = copy.deepcopy(self.contentparams)
  738.                 contentparams['value'] = output
  739.                 self.entries[-1][element + '_detail'] = contentparams
  740.             
  741.         elif (self.infeed or self.insource) and not (self.intextinput) and not (self.inimage):
  742.             context = self._getContext()
  743.             if element == 'description':
  744.                 element = 'subtitle'
  745.             
  746.             context[element] = output
  747.             if element == 'link':
  748.                 context['links'][-1]['href'] = output
  749.             elif self.incontent:
  750.                 contentparams = copy.deepcopy(self.contentparams)
  751.                 contentparams['value'] = output
  752.                 context[element + '_detail'] = contentparams
  753.             
  754.         
  755.         return output
  756.  
  757.     
  758.     def pushContent(self, tag, attrsD, defaultContentType, expectingText):
  759.         self.incontent += 1
  760.         self.contentparams = FeedParserDict({
  761.             'type': self.mapContentType(attrsD.get('type', defaultContentType)),
  762.             'language': self.lang,
  763.             'base': self.baseuri })
  764.         self.contentparams['base64'] = self._isBase64(attrsD, self.contentparams)
  765.         self.push(tag, expectingText)
  766.  
  767.     
  768.     def popContent(self, tag):
  769.         value = self.pop(tag)
  770.         self.incontent -= 1
  771.         self.contentparams.clear()
  772.         return value
  773.  
  774.     
  775.     def _mapToStandardPrefix(self, name):
  776.         colonpos = name.find(':')
  777.         if colonpos != -1:
  778.             prefix = name[:colonpos]
  779.             suffix = name[colonpos + 1:]
  780.             prefix = self.namespacemap.get(prefix, prefix)
  781.             name = prefix + ':' + suffix
  782.         
  783.         return name
  784.  
  785.     
  786.     def _getAttribute(self, attrsD, name):
  787.         return attrsD.get(self._mapToStandardPrefix(name))
  788.  
  789.     
  790.     def _isBase64(self, attrsD, contentparams):
  791.         if attrsD.get('mode', '') == 'base64':
  792.             return 1
  793.         if self.contentparams['type'].startswith('text/'):
  794.             return 0
  795.         if self.contentparams['type'].endswith('+xml'):
  796.             return 0
  797.         if self.contentparams['type'].endswith('/xml'):
  798.             return 0
  799.         return 1
  800.  
  801.     
  802.     def _itsAnHrefDamnIt(self, attrsD):
  803.         href = attrsD.get('url', attrsD.get('uri', attrsD.get('href', None)))
  804.         if href:
  805.             
  806.             try:
  807.                 del attrsD['url']
  808.             except KeyError:
  809.                 pass
  810.  
  811.             
  812.             try:
  813.                 del attrsD['uri']
  814.             except KeyError:
  815.                 pass
  816.  
  817.             attrsD['href'] = href
  818.         
  819.         return attrsD
  820.  
  821.     
  822.     def _save(self, key, value):
  823.         context = self._getContext()
  824.         context.setdefault(key, value)
  825.  
  826.     
  827.     def _start_rss(self, attrsD):
  828.         versionmap = {
  829.             '0.91': 'rss091u',
  830.             '0.92': 'rss092',
  831.             '0.93': 'rss093',
  832.             '0.94': 'rss094' }
  833.         if not self.version:
  834.             attr_version = attrsD.get('version', '')
  835.             version = versionmap.get(attr_version)
  836.             if version:
  837.                 self.version = version
  838.             elif attr_version.startswith('2.'):
  839.                 self.version = 'rss20'
  840.             else:
  841.                 self.version = 'rss'
  842.         
  843.  
  844.     
  845.     def _start_dlhottitles(self, attrsD):
  846.         self.version = 'hotrss'
  847.  
  848.     
  849.     def _start_channel(self, attrsD):
  850.         self.infeed = 1
  851.         self._cdf_common(attrsD)
  852.  
  853.     _start_feedinfo = _start_channel
  854.     
  855.     def _cdf_common(self, attrsD):
  856.         if attrsD.has_key('lastmod'):
  857.             self._start_modified({ })
  858.             self.elementstack[-1][-1] = attrsD['lastmod']
  859.             self._end_modified()
  860.         
  861.         if attrsD.has_key('href'):
  862.             self._start_link({ })
  863.             self.elementstack[-1][-1] = attrsD['href']
  864.             self._end_link()
  865.         
  866.  
  867.     
  868.     def _start_feed(self, attrsD):
  869.         self.infeed = 1
  870.         versionmap = {
  871.             '0.1': 'atom01',
  872.             '0.2': 'atom02',
  873.             '0.3': 'atom03' }
  874.         if not self.version:
  875.             attr_version = attrsD.get('version')
  876.             version = versionmap.get(attr_version)
  877.             if version:
  878.                 self.version = version
  879.             else:
  880.                 self.version = 'atom'
  881.         
  882.  
  883.     
  884.     def _end_channel(self):
  885.         self.infeed = 0
  886.  
  887.     _end_feed = _end_channel
  888.     
  889.     def _start_image(self, attrsD):
  890.         self.inimage = 1
  891.         self.push('image', 0)
  892.         context = self._getContext()
  893.         context.setdefault('image', FeedParserDict())
  894.  
  895.     
  896.     def _end_image(self):
  897.         self.pop('image')
  898.         self.inimage = 0
  899.  
  900.     
  901.     def _start_textinput(self, attrsD):
  902.         self.intextinput = 1
  903.         self.push('textinput', 0)
  904.         context = self._getContext()
  905.         context.setdefault('textinput', FeedParserDict())
  906.  
  907.     _start_textInput = _start_textinput
  908.     
  909.     def _end_textinput(self):
  910.         self.pop('textinput')
  911.         self.intextinput = 0
  912.  
  913.     _end_textInput = _end_textinput
  914.     
  915.     def _start_author(self, attrsD):
  916.         self.inauthor = 1
  917.         self.push('author', 1)
  918.  
  919.     _start_managingeditor = _start_author
  920.     _start_dc_author = _start_author
  921.     _start_dc_creator = _start_author
  922.     _start_itunes_author = _start_author
  923.     
  924.     def _end_author(self):
  925.         self.pop('author')
  926.         self.inauthor = 0
  927.         self._sync_author_detail()
  928.  
  929.     _end_managingeditor = _end_author
  930.     _end_dc_author = _end_author
  931.     _end_dc_creator = _end_author
  932.     _end_itunes_author = _end_author
  933.     
  934.     def _start_itunes_owner(self, attrsD):
  935.         self.inpublisher = 1
  936.         self.push('publisher', 0)
  937.  
  938.     
  939.     def _end_itunes_owner(self):
  940.         self.pop('publisher')
  941.         self.inpublisher = 0
  942.         self._sync_author_detail('publisher')
  943.  
  944.     
  945.     def _start_contributor(self, attrsD):
  946.         self.incontributor = 1
  947.         context = self._getContext()
  948.         context.setdefault('contributors', [])
  949.         context['contributors'].append(FeedParserDict())
  950.         self.push('contributor', 0)
  951.  
  952.     
  953.     def _end_contributor(self):
  954.         self.pop('contributor')
  955.         self.incontributor = 0
  956.  
  957.     
  958.     def _start_dc_contributor(self, attrsD):
  959.         self.incontributor = 1
  960.         context = self._getContext()
  961.         context.setdefault('contributors', [])
  962.         context['contributors'].append(FeedParserDict())
  963.         self.push('name', 0)
  964.  
  965.     
  966.     def _end_dc_contributor(self):
  967.         self._end_name()
  968.         self.incontributor = 0
  969.  
  970.     
  971.     def _start_name(self, attrsD):
  972.         self.push('name', 0)
  973.  
  974.     _start_itunes_name = _start_name
  975.     
  976.     def _end_name(self):
  977.         value = self.pop('name')
  978.         if self.inpublisher:
  979.             self._save_author('name', value, 'publisher')
  980.         elif self.inauthor:
  981.             self._save_author('name', value)
  982.         elif self.incontributor:
  983.             self._save_contributor('name', value)
  984.         elif self.intextinput:
  985.             context = self._getContext()
  986.             context['textinput']['name'] = value
  987.         
  988.  
  989.     _end_itunes_name = _end_name
  990.     
  991.     def _start_width(self, attrsD):
  992.         self.push('width', 0)
  993.  
  994.     
  995.     def _end_width(self):
  996.         value = self.pop('width')
  997.         
  998.         try:
  999.             value = int(value)
  1000.         except:
  1001.             value = 0
  1002.  
  1003.         if self.inimage:
  1004.             context = self._getContext()
  1005.             context['image']['width'] = value
  1006.         
  1007.  
  1008.     
  1009.     def _start_height(self, attrsD):
  1010.         self.push('height', 0)
  1011.  
  1012.     
  1013.     def _end_height(self):
  1014.         value = self.pop('height')
  1015.         
  1016.         try:
  1017.             value = int(value)
  1018.         except:
  1019.             value = 0
  1020.  
  1021.         if self.inimage:
  1022.             context = self._getContext()
  1023.             context['image']['height'] = value
  1024.         
  1025.  
  1026.     
  1027.     def _start_url(self, attrsD):
  1028.         self.push('href', 1)
  1029.  
  1030.     _start_homepage = _start_url
  1031.     _start_uri = _start_url
  1032.     
  1033.     def _end_url(self):
  1034.         value = self.pop('href')
  1035.         if self.inauthor:
  1036.             self._save_author('href', value)
  1037.         elif self.incontributor:
  1038.             self._save_contributor('href', value)
  1039.         elif self.inimage:
  1040.             context = self._getContext()
  1041.             context['image']['href'] = value
  1042.         elif self.intextinput:
  1043.             context = self._getContext()
  1044.             context['textinput']['link'] = value
  1045.         
  1046.  
  1047.     _end_homepage = _end_url
  1048.     _end_uri = _end_url
  1049.     
  1050.     def _start_email(self, attrsD):
  1051.         self.push('email', 0)
  1052.  
  1053.     _start_itunes_email = _start_email
  1054.     
  1055.     def _end_email(self):
  1056.         value = self.pop('email')
  1057.         if self.inpublisher:
  1058.             self._save_author('email', value, 'publisher')
  1059.         elif self.inauthor:
  1060.             self._save_author('email', value)
  1061.         elif self.incontributor:
  1062.             self._save_contributor('email', value)
  1063.         
  1064.  
  1065.     _end_itunes_email = _end_email
  1066.     
  1067.     def _getContext(self):
  1068.         if self.insource:
  1069.             context = self.sourcedata
  1070.         elif self.inentry:
  1071.             context = self.entries[-1]
  1072.         else:
  1073.             context = self.feeddata
  1074.         return context
  1075.  
  1076.     
  1077.     def _save_author(self, key, value, prefix = 'author'):
  1078.         context = self._getContext()
  1079.         context.setdefault(prefix + '_detail', FeedParserDict())
  1080.         context[prefix + '_detail'][key] = value
  1081.         self._sync_author_detail()
  1082.  
  1083.     
  1084.     def _save_contributor(self, key, value):
  1085.         context = self._getContext()
  1086.         context.setdefault('contributors', [
  1087.             FeedParserDict()])
  1088.         context['contributors'][-1][key] = value
  1089.  
  1090.     
  1091.     def _sync_author_detail(self, key = 'author'):
  1092.         context = self._getContext()
  1093.         detail = context.get('%s_detail' % key)
  1094.         if detail:
  1095.             name = detail.get('name')
  1096.             email = detail.get('email')
  1097.             if name and email:
  1098.                 context[key] = '%s (%s)' % (name, email)
  1099.             elif name:
  1100.                 context[key] = name
  1101.             elif email:
  1102.                 context[key] = email
  1103.             
  1104.         else:
  1105.             author = context.get(key)
  1106.             if not author:
  1107.                 return None
  1108.             emailmatch = re.search('(([a-zA-Z0-9\\_\\-\\.\\+]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?))', author)
  1109.             if not emailmatch:
  1110.                 return None
  1111.             email = emailmatch.group(0)
  1112.             author = author.replace(email, '')
  1113.             author = author.replace('()', '')
  1114.             author = author.strip()
  1115.             if author and author[-1] == ')':
  1116.                 author = author[:-1]
  1117.             
  1118.             author = author.strip()
  1119.             context.setdefault('%s_detail' % key, FeedParserDict())
  1120.             context['%s_detail' % key]['name'] = author
  1121.             context['%s_detail' % key]['email'] = email
  1122.  
  1123.     
  1124.     def _start_subtitle(self, attrsD):
  1125.         self.pushContent('subtitle', attrsD, 'text/plain', 1)
  1126.  
  1127.     _start_tagline = _start_subtitle
  1128.     _start_itunes_subtitle = _start_subtitle
  1129.     
  1130.     def _end_subtitle(self):
  1131.         self.popContent('subtitle')
  1132.  
  1133.     _end_tagline = _end_subtitle
  1134.     _end_itunes_subtitle = _end_subtitle
  1135.     
  1136.     def _start_rights(self, attrsD):
  1137.         self.pushContent('rights', attrsD, 'text/plain', 1)
  1138.  
  1139.     _start_dc_rights = _start_rights
  1140.     _start_copyright = _start_rights
  1141.     
  1142.     def _end_rights(self):
  1143.         self.popContent('rights')
  1144.  
  1145.     _end_dc_rights = _end_rights
  1146.     _end_copyright = _end_rights
  1147.     
  1148.     def _start_item(self, attrsD):
  1149.         self.entries.append(FeedParserDict())
  1150.         self.push('item', 0)
  1151.         self.inentry = 1
  1152.         self.guidislink = 0
  1153.         id = self._getAttribute(attrsD, 'rdf:about')
  1154.         if id:
  1155.             context = self._getContext()
  1156.             context['id'] = id
  1157.         
  1158.         self._cdf_common(attrsD)
  1159.  
  1160.     _start_entry = _start_item
  1161.     _start_product = _start_item
  1162.     
  1163.     def _end_item(self):
  1164.         self.pop('item')
  1165.         self.inentry = 0
  1166.  
  1167.     _end_entry = _end_item
  1168.     
  1169.     def _start_dc_language(self, attrsD):
  1170.         self.push('language', 1)
  1171.  
  1172.     _start_language = _start_dc_language
  1173.     
  1174.     def _end_dc_language(self):
  1175.         self.lang = self.pop('language')
  1176.  
  1177.     _end_language = _end_dc_language
  1178.     
  1179.     def _start_dc_publisher(self, attrsD):
  1180.         self.push('publisher', 1)
  1181.  
  1182.     _start_webmaster = _start_dc_publisher
  1183.     
  1184.     def _end_dc_publisher(self):
  1185.         self.pop('publisher')
  1186.         self._sync_author_detail('publisher')
  1187.  
  1188.     _end_webmaster = _end_dc_publisher
  1189.     
  1190.     def _start_published(self, attrsD):
  1191.         self.push('published', 1)
  1192.  
  1193.     _start_dcterms_issued = _start_published
  1194.     _start_issued = _start_published
  1195.     
  1196.     def _end_published(self):
  1197.         value = self.pop('published')
  1198.         self._save('published_parsed', _parse_date(value))
  1199.  
  1200.     _end_dcterms_issued = _end_published
  1201.     _end_issued = _end_published
  1202.     
  1203.     def _start_updated(self, attrsD):
  1204.         self.push('updated', 1)
  1205.  
  1206.     _start_modified = _start_updated
  1207.     _start_dcterms_modified = _start_updated
  1208.     _start_pubdate = _start_updated
  1209.     _start_dc_date = _start_updated
  1210.     
  1211.     def _end_updated(self):
  1212.         value = self.pop('updated')
  1213.         parsed_value = _parse_date(value)
  1214.         self._save('updated_parsed', parsed_value)
  1215.  
  1216.     _end_modified = _end_updated
  1217.     _end_dcterms_modified = _end_updated
  1218.     _end_pubdate = _end_updated
  1219.     _end_dc_date = _end_updated
  1220.     
  1221.     def _start_created(self, attrsD):
  1222.         self.push('created', 1)
  1223.  
  1224.     _start_dcterms_created = _start_created
  1225.     
  1226.     def _end_created(self):
  1227.         value = self.pop('created')
  1228.         self._save('created_parsed', _parse_date(value))
  1229.  
  1230.     _end_dcterms_created = _end_created
  1231.     
  1232.     def _start_expirationdate(self, attrsD):
  1233.         self.push('expired', 1)
  1234.  
  1235.     
  1236.     def _end_expirationdate(self):
  1237.         self._save('expired_parsed', _parse_date(self.pop('expired')))
  1238.  
  1239.     
  1240.     def _start_cc_license(self, attrsD):
  1241.         self.push('license', 1)
  1242.         value = self._getAttribute(attrsD, 'rdf:resource')
  1243.         if value:
  1244.             self.elementstack[-1][2].append(value)
  1245.         
  1246.         self.pop('license')
  1247.  
  1248.     
  1249.     def _start_creativecommons_license(self, attrsD):
  1250.         self.push('license', 1)
  1251.  
  1252.     
  1253.     def _end_creativecommons_license(self):
  1254.         self.pop('license')
  1255.  
  1256.     
  1257.     def _addTag(self, term, scheme, label):
  1258.         context = self._getContext()
  1259.         tags = context.setdefault('tags', [])
  1260.         if not term and not scheme and not label:
  1261.             return None
  1262.         value = FeedParserDict({
  1263.             'term': term,
  1264.             'scheme': scheme,
  1265.             'label': label })
  1266.         if value not in tags:
  1267.             tags.append(FeedParserDict({
  1268.                 'term': term,
  1269.                 'scheme': scheme,
  1270.                 'label': label }))
  1271.         
  1272.  
  1273.     
  1274.     def _start_category(self, attrsD):
  1275.         if _debug:
  1276.             sys.stderr.write('entering _start_category with %s\n' % repr(attrsD))
  1277.         
  1278.         term = attrsD.get('term')
  1279.         scheme = attrsD.get('scheme', attrsD.get('domain'))
  1280.         label = attrsD.get('label')
  1281.         self._addTag(term, scheme, label)
  1282.         self.push('category', 1)
  1283.  
  1284.     _start_dc_subject = _start_category
  1285.     _start_keywords = _start_category
  1286.     
  1287.     def _end_itunes_keywords(self):
  1288.         for term in self.pop('itunes_keywords').split():
  1289.             self._addTag(term, 'http://www.itunes.com/', None)
  1290.         
  1291.  
  1292.     
  1293.     def _start_itunes_category(self, attrsD):
  1294.         self._addTag(attrsD.get('text'), 'http://www.itunes.com/', None)
  1295.         self.push('category', 1)
  1296.  
  1297.     
  1298.     def _end_category(self):
  1299.         value = self.pop('category')
  1300.         if not value:
  1301.             return None
  1302.         context = self._getContext()
  1303.         tags = context['tags']
  1304.         if value and len(tags) and not tags[-1]['term']:
  1305.             tags[-1]['term'] = value
  1306.         else:
  1307.             self._addTag(value, None, None)
  1308.  
  1309.     _end_dc_subject = _end_category
  1310.     _end_keywords = _end_category
  1311.     _end_itunes_category = _end_category
  1312.     
  1313.     def _start_cloud(self, attrsD):
  1314.         self._getContext()['cloud'] = FeedParserDict(attrsD)
  1315.  
  1316.     
  1317.     def _start_link(self, attrsD):
  1318.         attrsD.setdefault('rel', 'alternate')
  1319.         attrsD.setdefault('type', 'text/html')
  1320.         attrsD = self._itsAnHrefDamnIt(attrsD)
  1321.         if attrsD.has_key('href'):
  1322.             attrsD['href'] = self.resolveURI(attrsD['href'])
  1323.         
  1324.         if not self.infeed and self.inentry:
  1325.             pass
  1326.         expectingText = self.insource
  1327.         context = self._getContext()
  1328.         context.setdefault('links', [])
  1329.         context['links'].append(FeedParserDict(attrsD))
  1330.         if attrsD['rel'] == 'enclosure':
  1331.             self._start_enclosure(attrsD)
  1332.         
  1333.         if attrsD.has_key('href'):
  1334.             expectingText = 0
  1335.             if attrsD.get('rel') == 'alternate' and self.mapContentType(attrsD.get('type')) in self.html_types:
  1336.                 context['link'] = attrsD['href']
  1337.             
  1338.         else:
  1339.             self.push('link', expectingText)
  1340.  
  1341.     _start_producturl = _start_link
  1342.     
  1343.     def _end_link(self):
  1344.         value = self.pop('link')
  1345.         context = self._getContext()
  1346.         if self.intextinput:
  1347.             context['textinput']['link'] = value
  1348.         
  1349.         if self.inimage:
  1350.             context['image']['link'] = value
  1351.         
  1352.  
  1353.     _end_producturl = _end_link
  1354.     
  1355.     def _start_guid(self, attrsD):
  1356.         self.guidislink = attrsD.get('ispermalink', 'true') == 'true'
  1357.         self.push('id', 1)
  1358.  
  1359.     
  1360.     def _end_guid(self):
  1361.         value = self.pop('id')
  1362.         if self.guidislink:
  1363.             pass
  1364.         self._save('guidislink', not self._getContext().has_key('link'))
  1365.         if self.guidislink:
  1366.             self._save('link', value)
  1367.         
  1368.  
  1369.     
  1370.     def _start_title(self, attrsD):
  1371.         if not self.infeed and self.inentry:
  1372.             pass
  1373.         self.pushContent('title', attrsD, 'text/plain', self.insource)
  1374.  
  1375.     
  1376.     def _start_title_low_pri(self, attrsD):
  1377.         if not self._getContext().has_key('title'):
  1378.             self._start_title(attrsD)
  1379.         
  1380.  
  1381.     _start_dc_title = _start_title_low_pri
  1382.     _start_media_title = _start_title_low_pri
  1383.     
  1384.     def _end_title(self):
  1385.         value = self.popContent('title')
  1386.         context = self._getContext()
  1387.         if self.intextinput:
  1388.             context['textinput']['title'] = value
  1389.         elif self.inimage:
  1390.             context['image']['title'] = value
  1391.         
  1392.  
  1393.     
  1394.     def _end_title_low_pri(self):
  1395.         if not self._getContext().has_key('title'):
  1396.             self._end_title()
  1397.         
  1398.  
  1399.     _end_dc_title = _end_title_low_pri
  1400.     _end_media_title = _end_title_low_pri
  1401.     
  1402.     def _start_description(self, attrsD):
  1403.         context = self._getContext()
  1404.         if context.has_key('summary'):
  1405.             self._summaryKey = 'content'
  1406.             self._start_content(attrsD)
  1407.         elif not self.infeed and self.inentry:
  1408.             pass
  1409.         self.pushContent('description', attrsD, 'text/html', self.insource)
  1410.  
  1411.     
  1412.     def _start_abstract(self, attrsD):
  1413.         if not self.infeed and self.inentry:
  1414.             pass
  1415.         self.pushContent('description', attrsD, 'text/plain', self.insource)
  1416.  
  1417.     
  1418.     def _end_description(self):
  1419.         if self._summaryKey == 'content':
  1420.             self._end_content()
  1421.         else:
  1422.             value = self.popContent('description')
  1423.             context = self._getContext()
  1424.             if self.intextinput:
  1425.                 context['textinput']['description'] = value
  1426.             elif self.inimage:
  1427.                 context['image']['description'] = value
  1428.             
  1429.         self._summaryKey = None
  1430.  
  1431.     _end_abstract = _end_description
  1432.     
  1433.     def _start_info(self, attrsD):
  1434.         self.pushContent('info', attrsD, 'text/plain', 1)
  1435.  
  1436.     _start_feedburner_browserfriendly = _start_info
  1437.     
  1438.     def _end_info(self):
  1439.         self.popContent('info')
  1440.  
  1441.     _end_feedburner_browserfriendly = _end_info
  1442.     
  1443.     def _start_generator(self, attrsD):
  1444.         if attrsD:
  1445.             attrsD = self._itsAnHrefDamnIt(attrsD)
  1446.             if attrsD.has_key('href'):
  1447.                 attrsD['href'] = self.resolveURI(attrsD['href'])
  1448.             
  1449.         
  1450.         self._getContext()['generator_detail'] = FeedParserDict(attrsD)
  1451.         self.push('generator', 1)
  1452.  
  1453.     
  1454.     def _end_generator(self):
  1455.         value = self.pop('generator')
  1456.         context = self._getContext()
  1457.         if context.has_key('generator_detail'):
  1458.             context['generator_detail']['name'] = value
  1459.         
  1460.  
  1461.     
  1462.     def _start_admin_generatoragent(self, attrsD):
  1463.         self.push('generator', 1)
  1464.         value = self._getAttribute(attrsD, 'rdf:resource')
  1465.         if value:
  1466.             self.elementstack[-1][2].append(value)
  1467.         
  1468.         self.pop('generator')
  1469.         self._getContext()['generator_detail'] = FeedParserDict({
  1470.             'href': value })
  1471.  
  1472.     
  1473.     def _start_admin_errorreportsto(self, attrsD):
  1474.         self.push('errorreportsto', 1)
  1475.         value = self._getAttribute(attrsD, 'rdf:resource')
  1476.         if value:
  1477.             self.elementstack[-1][2].append(value)
  1478.         
  1479.         self.pop('errorreportsto')
  1480.  
  1481.     
  1482.     def _start_summary(self, attrsD):
  1483.         context = self._getContext()
  1484.         if context.has_key('summary'):
  1485.             self._summaryKey = 'content'
  1486.             self._start_content(attrsD)
  1487.         else:
  1488.             self._summaryKey = 'summary'
  1489.             self.pushContent(self._summaryKey, attrsD, 'text/plain', 1)
  1490.  
  1491.     _start_itunes_summary = _start_summary
  1492.     
  1493.     def _end_summary(self):
  1494.         if self._summaryKey == 'content':
  1495.             self._end_content()
  1496.         elif not self._summaryKey:
  1497.             pass
  1498.         self.popContent('summary')
  1499.         self._summaryKey = None
  1500.  
  1501.     _end_itunes_summary = _end_summary
  1502.     
  1503.     def _start_enclosure(self, attrsD):
  1504.         attrsD = self._itsAnHrefDamnIt(attrsD)
  1505.         self._getContext().setdefault('enclosures', []).append(FeedParserDict(attrsD))
  1506.         href = attrsD.get('href')
  1507.         if href:
  1508.             context = self._getContext()
  1509.             if not context.get('id'):
  1510.                 context['id'] = href
  1511.             
  1512.         
  1513.  
  1514.     
  1515.     def _start_source(self, attrsD):
  1516.         self.insource = 1
  1517.  
  1518.     
  1519.     def _end_source(self):
  1520.         self.insource = 0
  1521.         self._getContext()['source'] = copy.deepcopy(self.sourcedata)
  1522.         self.sourcedata.clear()
  1523.  
  1524.     
  1525.     def _start_content(self, attrsD):
  1526.         self.pushContent('content', attrsD, 'text/plain', 1)
  1527.         src = attrsD.get('src')
  1528.         if src:
  1529.             self.contentparams['src'] = src
  1530.         
  1531.         self.push('content', 1)
  1532.  
  1533.     
  1534.     def _start_prodlink(self, attrsD):
  1535.         self.pushContent('content', attrsD, 'text/html', 1)
  1536.  
  1537.     
  1538.     def _start_body(self, attrsD):
  1539.         self.pushContent('content', attrsD, 'application/xhtml+xml', 1)
  1540.  
  1541.     _start_xhtml_body = _start_body
  1542.     
  1543.     def _start_content_encoded(self, attrsD):
  1544.         self.pushContent('content', attrsD, 'text/html', 1)
  1545.  
  1546.     _start_fullitem = _start_content_encoded
  1547.     
  1548.     def _end_content(self):
  1549.         copyToDescription = self.mapContentType(self.contentparams.get('type')) in [
  1550.             'text/plain'] + self.html_types
  1551.         value = self.popContent('content')
  1552.         if copyToDescription:
  1553.             self._save('description', value)
  1554.         
  1555.  
  1556.     _end_body = _end_content
  1557.     _end_xhtml_body = _end_content
  1558.     _end_content_encoded = _end_content
  1559.     _end_fullitem = _end_content
  1560.     _end_prodlink = _end_content
  1561.     
  1562.     def _start_itunes_image(self, attrsD):
  1563.         self.push('itunes_image', 0)
  1564.         self._getContext()['image'] = FeedParserDict({
  1565.             'href': attrsD.get('href') })
  1566.  
  1567.     _start_itunes_link = _start_itunes_image
  1568.     
  1569.     def _end_itunes_block(self):
  1570.         value = self.pop('itunes_block', 0)
  1571.         if not value == 'yes' or 1:
  1572.             pass
  1573.         self._getContext()['itunes_block'] = 0
  1574.  
  1575.     
  1576.     def _end_itunes_explicit(self):
  1577.         value = self.pop('itunes_explicit', 0)
  1578.         if not value == 'yes' or 1:
  1579.             pass
  1580.         self._getContext()['itunes_explicit'] = 0
  1581.  
  1582.  
  1583. if _XML_AVAILABLE:
  1584.     
  1585.     class _StrictFeedParser(_FeedParserMixin, xml.sax.handler.ContentHandler):
  1586.         
  1587.         def __init__(self, baseuri, baselang, encoding):
  1588.             if _debug:
  1589.                 sys.stderr.write('trying StrictFeedParser\n')
  1590.             
  1591.             xml.sax.handler.ContentHandler.__init__(self)
  1592.             _FeedParserMixin.__init__(self, baseuri, baselang, encoding)
  1593.             self.bozo = 0
  1594.             self.exc = None
  1595.  
  1596.         
  1597.         def startPrefixMapping(self, prefix, uri):
  1598.             self.trackNamespace(prefix, uri)
  1599.  
  1600.         
  1601.         def startElementNS(self, name, qname, attrs):
  1602.             (namespace, localname) = name
  1603.             if not namespace:
  1604.                 pass
  1605.             lowernamespace = str('').lower()
  1606.             if lowernamespace.find('backend.userland.com/rss') != -1:
  1607.                 namespace = 'http://backend.userland.com/rss'
  1608.                 lowernamespace = namespace
  1609.             
  1610.             if qname and qname.find(':') > 0:
  1611.                 givenprefix = qname.split(':')[0]
  1612.             else:
  1613.                 givenprefix = None
  1614.             prefix = self._matchnamespaces.get(lowernamespace, givenprefix)
  1615.             if givenprefix:
  1616.                 if (prefix == None or prefix == '' or lowernamespace == '') and not self.namespacesInUse.has_key(givenprefix):
  1617.                     raise UndeclaredNamespace, "'%s' is not associated with a namespace" % givenprefix
  1618.             not self.namespacesInUse.has_key(givenprefix)
  1619.             if prefix:
  1620.                 localname = prefix + ':' + localname
  1621.             
  1622.             localname = str(localname).lower()
  1623.             if _debug:
  1624.                 sys.stderr.write('startElementNS: qname = %s, namespace = %s, givenprefix = %s, prefix = %s, attrs = %s, localname = %s\n' % (qname, namespace, givenprefix, prefix, attrs.items(), localname))
  1625.             
  1626.             attrsD = { }
  1627.             for namespace, attrlocalname in attrs._attrs.items():
  1628.                 attrvalue = None
  1629.                 if not namespace:
  1630.                     pass
  1631.                 lowernamespace = ''.lower()
  1632.                 prefix = self._matchnamespaces.get(lowernamespace, '')
  1633.                 if prefix:
  1634.                     attrlocalname = prefix + ':' + attrlocalname
  1635.                 
  1636.                 attrsD[str(attrlocalname).lower()] = attrvalue
  1637.             
  1638.             for qname in attrs.getQNames():
  1639.                 attrsD[str(qname).lower()] = attrs.getValueByQName(qname)
  1640.             
  1641.             self.unknown_starttag(localname, attrsD.items())
  1642.  
  1643.         
  1644.         def characters(self, text):
  1645.             self.handle_data(text)
  1646.  
  1647.         
  1648.         def endElementNS(self, name, qname):
  1649.             (namespace, localname) = name
  1650.             if not namespace:
  1651.                 pass
  1652.             lowernamespace = str('').lower()
  1653.             if qname and qname.find(':') > 0:
  1654.                 givenprefix = qname.split(':')[0]
  1655.             else:
  1656.                 givenprefix = ''
  1657.             prefix = self._matchnamespaces.get(lowernamespace, givenprefix)
  1658.             if prefix:
  1659.                 localname = prefix + ':' + localname
  1660.             
  1661.             localname = str(localname).lower()
  1662.             self.unknown_endtag(localname)
  1663.  
  1664.         
  1665.         def error(self, exc):
  1666.             self.bozo = 1
  1667.             self.exc = exc
  1668.  
  1669.         
  1670.         def fatalError(self, exc):
  1671.             self.error(exc)
  1672.             raise exc
  1673.  
  1674.  
  1675.  
  1676.  
  1677. class _BaseHTMLProcessor(sgmllib.SGMLParser):
  1678.     elements_no_end_tag = [
  1679.         'area',
  1680.         'base',
  1681.         'basefont',
  1682.         'br',
  1683.         'col',
  1684.         'frame',
  1685.         'hr',
  1686.         'img',
  1687.         'input',
  1688.         'isindex',
  1689.         'link',
  1690.         'meta',
  1691.         'param']
  1692.     
  1693.     def __init__(self, encoding):
  1694.         self.encoding = encoding
  1695.         if _debug:
  1696.             sys.stderr.write('entering BaseHTMLProcessor, encoding=%s\n' % self.encoding)
  1697.         
  1698.         sgmllib.SGMLParser.__init__(self)
  1699.  
  1700.     
  1701.     def reset(self):
  1702.         self.pieces = []
  1703.         sgmllib.SGMLParser.reset(self)
  1704.  
  1705.     
  1706.     def _shorttag_replace(self, match):
  1707.         tag = match.group(1)
  1708.         if tag in self.elements_no_end_tag:
  1709.             return '<' + tag + ' />'
  1710.         return '<' + tag + '></' + tag + '>'
  1711.  
  1712.     
  1713.     def feed(self, data):
  1714.         data = re.compile('<!((?!DOCTYPE|--|\\[))', re.IGNORECASE).sub('<!\\1', data)
  1715.         data = re.sub('<([^<\\s]+?)\\s*/>', self._shorttag_replace, data)
  1716.         data = data.replace(''', "'")
  1717.         data = data.replace('"', '"')
  1718.         if self.encoding and type(data) == type(u''):
  1719.             data = data.encode(self.encoding)
  1720.         
  1721.         sgmllib.SGMLParser.feed(self, data)
  1722.  
  1723.     
  1724.     def normalize_attrs(self, attrs):
  1725.         attrs = [ (k.lower(), v) for k, v in attrs ]
  1726.         attrs = [ (k, v) for k, v in attrs ]
  1727.         return attrs
  1728.  
  1729.     
  1730.     def unknown_starttag(self, tag, attrs):
  1731.         if _debug:
  1732.             sys.stderr.write('_BaseHTMLProcessor, unknown_starttag, tag=%s\n' % tag)
  1733.         
  1734.         uattrs = []
  1735.         for key, value in attrs:
  1736.             if type(value) != type(u''):
  1737.                 value = unicode(value, self.encoding, errors = 'replace')
  1738.             
  1739.             uattrs.append((unicode(key, self.encoding), value))
  1740.         
  1741.         strattrs = []([ u' %s="%s"' % (key, value) for key, value in uattrs ]).encode(self.encoding)
  1742.  
  1743.     
  1744.     def unknown_endtag(self, tag):
  1745.         if tag not in self.elements_no_end_tag:
  1746.             self.pieces.append('</%(tag)s>' % locals())
  1747.         
  1748.  
  1749.     
  1750.     def handle_charref(self, ref):
  1751.         self.pieces.append('&#%(ref)s;' % locals())
  1752.  
  1753.     
  1754.     def handle_entityref(self, ref):
  1755.         self.pieces.append('&%(ref)s;' % locals())
  1756.  
  1757.     
  1758.     def handle_data(self, text):
  1759.         if _debug:
  1760.             sys.stderr.write('_BaseHTMLProcessor, handle_text, text=%s\n' % text)
  1761.         
  1762.         self.pieces.append(text)
  1763.  
  1764.     
  1765.     def handle_comment(self, text):
  1766.         self.pieces.append('<!--%(text)s-->' % locals())
  1767.  
  1768.     
  1769.     def handle_pi(self, text):
  1770.         self.pieces.append('<?%(text)s>' % locals())
  1771.  
  1772.     
  1773.     def handle_decl(self, text):
  1774.         self.pieces.append('<!%(text)s>' % locals())
  1775.  
  1776.     _new_declname_match = re.compile('[a-zA-Z][-_.a-zA-Z0-9:]*\\s*').match
  1777.     
  1778.     def _scan_name(self, i, declstartpos):
  1779.         rawdata = self.rawdata
  1780.         n = len(rawdata)
  1781.         if i == n:
  1782.             return (None, -1)
  1783.         m = self._new_declname_match(rawdata, i)
  1784.         if m:
  1785.             s = m.group()
  1786.             name = s.strip()
  1787.             if i + len(s) == n:
  1788.                 return (None, -1)
  1789.             return (name.lower(), m.end())
  1790.         self.handle_data(rawdata)
  1791.         return (None, -1)
  1792.  
  1793.     
  1794.     def output(self):
  1795.         '''Return processed HTML as a single string'''
  1796.         return []([ str(p) for p in self.pieces ])
  1797.  
  1798.  
  1799.  
  1800. class _LooseFeedParser(_FeedParserMixin, _BaseHTMLProcessor):
  1801.     
  1802.     def __init__(self, baseuri, baselang, encoding):
  1803.         sgmllib.SGMLParser.__init__(self)
  1804.         _FeedParserMixin.__init__(self, baseuri, baselang, encoding)
  1805.  
  1806.     
  1807.     def decodeEntities(self, element, data):
  1808.         data = data.replace('<', '<')
  1809.         data = data.replace('<', '<')
  1810.         data = data.replace('>', '>')
  1811.         data = data.replace('>', '>')
  1812.         data = data.replace('&', '&')
  1813.         data = data.replace('&', '&')
  1814.         data = data.replace('"', '"')
  1815.         data = data.replace('"', '"')
  1816.         data = data.replace(''', ''')
  1817.         data = data.replace(''', ''')
  1818.         if self.contentparams.has_key('type') and not self.contentparams.get('type', 'xml').endswith('xml'):
  1819.             data = data.replace('<', '<')
  1820.             data = data.replace('>', '>')
  1821.             data = data.replace('&', '&')
  1822.             data = data.replace('"', '"')
  1823.             data = data.replace(''', "'")
  1824.         
  1825.         return data
  1826.  
  1827.  
  1828.  
  1829. class _RelativeURIResolver(_BaseHTMLProcessor):
  1830.     relative_uris = [
  1831.         ('a', 'href'),
  1832.         ('applet', 'codebase'),
  1833.         ('area', 'href'),
  1834.         ('blockquote', 'cite'),
  1835.         ('body', 'background'),
  1836.         ('del', 'cite'),
  1837.         ('form', 'action'),
  1838.         ('frame', 'longdesc'),
  1839.         ('frame', 'src'),
  1840.         ('iframe', 'longdesc'),
  1841.         ('iframe', 'src'),
  1842.         ('head', 'profile'),
  1843.         ('img', 'longdesc'),
  1844.         ('img', 'src'),
  1845.         ('img', 'usemap'),
  1846.         ('input', 'src'),
  1847.         ('input', 'usemap'),
  1848.         ('ins', 'cite'),
  1849.         ('link', 'href'),
  1850.         ('object', 'classid'),
  1851.         ('object', 'codebase'),
  1852.         ('object', 'data'),
  1853.         ('object', 'usemap'),
  1854.         ('q', 'cite'),
  1855.         ('script', 'src')]
  1856.     
  1857.     def __init__(self, baseuri, encoding):
  1858.         _BaseHTMLProcessor.__init__(self, encoding)
  1859.         self.baseuri = baseuri
  1860.  
  1861.     
  1862.     def resolveURI(self, uri):
  1863.         return _urljoin(self.baseuri, uri)
  1864.  
  1865.     
  1866.     def unknown_starttag(self, tag, attrs):
  1867.         attrs = self.normalize_attrs(attrs)
  1868.         attrs = [ (key, value) for key, value in attrs ]
  1869.         _BaseHTMLProcessor.unknown_starttag(self, tag, attrs)
  1870.  
  1871.  
  1872.  
  1873. def _resolveRelativeURIs(htmlSource, baseURI, encoding):
  1874.     if _debug:
  1875.         sys.stderr.write('entering _resolveRelativeURIs\n')
  1876.     
  1877.     p = _RelativeURIResolver(baseURI, encoding)
  1878.     p.feed(htmlSource)
  1879.     return p.output()
  1880.  
  1881.  
  1882. class _HTMLSanitizer(_BaseHTMLProcessor):
  1883.     acceptable_elements = [
  1884.         'a',
  1885.         'abbr',
  1886.         'acronym',
  1887.         'address',
  1888.         'area',
  1889.         'b',
  1890.         'big',
  1891.         'blockquote',
  1892.         'br',
  1893.         'button',
  1894.         'caption',
  1895.         'center',
  1896.         'cite',
  1897.         'code',
  1898.         'col',
  1899.         'colgroup',
  1900.         'dd',
  1901.         'del',
  1902.         'dfn',
  1903.         'dir',
  1904.         'div',
  1905.         'dl',
  1906.         'dt',
  1907.         'em',
  1908.         'fieldset',
  1909.         'font',
  1910.         'form',
  1911.         'h1',
  1912.         'h2',
  1913.         'h3',
  1914.         'h4',
  1915.         'h5',
  1916.         'h6',
  1917.         'hr',
  1918.         'i',
  1919.         'img',
  1920.         'input',
  1921.         'ins',
  1922.         'kbd',
  1923.         'label',
  1924.         'legend',
  1925.         'li',
  1926.         'map',
  1927.         'menu',
  1928.         'ol',
  1929.         'optgroup',
  1930.         'option',
  1931.         'p',
  1932.         'pre',
  1933.         'q',
  1934.         's',
  1935.         'samp',
  1936.         'select',
  1937.         'small',
  1938.         'span',
  1939.         'strike',
  1940.         'strong',
  1941.         'sub',
  1942.         'sup',
  1943.         'table',
  1944.         'tbody',
  1945.         'td',
  1946.         'textarea',
  1947.         'tfoot',
  1948.         'th',
  1949.         'thead',
  1950.         'tr',
  1951.         'tt',
  1952.         'u',
  1953.         'ul',
  1954.         'var']
  1955.     acceptable_attributes = [
  1956.         'abbr',
  1957.         'accept',
  1958.         'accept-charset',
  1959.         'accesskey',
  1960.         'action',
  1961.         'align',
  1962.         'alt',
  1963.         'axis',
  1964.         'border',
  1965.         'cellpadding',
  1966.         'cellspacing',
  1967.         'char',
  1968.         'charoff',
  1969.         'charset',
  1970.         'checked',
  1971.         'cite',
  1972.         'class',
  1973.         'clear',
  1974.         'cols',
  1975.         'colspan',
  1976.         'color',
  1977.         'compact',
  1978.         'coords',
  1979.         'datetime',
  1980.         'dir',
  1981.         'disabled',
  1982.         'enctype',
  1983.         'for',
  1984.         'frame',
  1985.         'headers',
  1986.         'height',
  1987.         'href',
  1988.         'hreflang',
  1989.         'hspace',
  1990.         'id',
  1991.         'ismap',
  1992.         'label',
  1993.         'lang',
  1994.         'longdesc',
  1995.         'maxlength',
  1996.         'media',
  1997.         'method',
  1998.         'multiple',
  1999.         'name',
  2000.         'nohref',
  2001.         'noshade',
  2002.         'nowrap',
  2003.         'prompt',
  2004.         'readonly',
  2005.         'rel',
  2006.         'rev',
  2007.         'rows',
  2008.         'rowspan',
  2009.         'rules',
  2010.         'scope',
  2011.         'selected',
  2012.         'shape',
  2013.         'size',
  2014.         'span',
  2015.         'src',
  2016.         'start',
  2017.         'summary',
  2018.         'tabindex',
  2019.         'target',
  2020.         'title',
  2021.         'type',
  2022.         'usemap',
  2023.         'valign',
  2024.         'value',
  2025.         'vspace',
  2026.         'width']
  2027.     unacceptable_elements_with_end_tag = [
  2028.         'script',
  2029.         'applet']
  2030.     
  2031.     def reset(self):
  2032.         _BaseHTMLProcessor.reset(self)
  2033.         self.unacceptablestack = 0
  2034.  
  2035.     
  2036.     def unknown_starttag(self, tag, attrs):
  2037.         if tag not in self.acceptable_elements:
  2038.             if tag in self.unacceptable_elements_with_end_tag:
  2039.                 self.unacceptablestack += 1
  2040.             
  2041.             return None
  2042.         attrs = self.normalize_attrs(attrs)
  2043.         attrs = _[1]
  2044.         _BaseHTMLProcessor.unknown_starttag(self, tag, attrs)
  2045.  
  2046.     
  2047.     def unknown_endtag(self, tag):
  2048.         if tag not in self.acceptable_elements:
  2049.             if tag in self.unacceptable_elements_with_end_tag:
  2050.                 self.unacceptablestack -= 1
  2051.             
  2052.             return None
  2053.         _BaseHTMLProcessor.unknown_endtag(self, tag)
  2054.  
  2055.     
  2056.     def handle_pi(self, text):
  2057.         pass
  2058.  
  2059.     
  2060.     def handle_decl(self, text):
  2061.         pass
  2062.  
  2063.     
  2064.     def handle_data(self, text):
  2065.         if not self.unacceptablestack:
  2066.             _BaseHTMLProcessor.handle_data(self, text)
  2067.         
  2068.  
  2069.  
  2070.  
  2071. def _sanitizeHTML(htmlSource, encoding):
  2072.     p = _HTMLSanitizer(encoding)
  2073.     p.feed(htmlSource)
  2074.     data = p.output()
  2075.     if TIDY_MARKUP:
  2076.         _tidy = None
  2077.         for tidy_interface in PREFERRED_TIDY_INTERFACES:
  2078.             
  2079.             try:
  2080.                 if tidy_interface == 'uTidy':
  2081.                     _utidy = parseString
  2082.                     import tidy
  2083.                     
  2084.                     def _tidy(data, **kwargs):
  2085.                         return str(_utidy(data, **kwargs))
  2086.  
  2087.                     break
  2088.                 elif tidy_interface == 'mxTidy':
  2089.                     _mxtidy = Tidy
  2090.                     import mx.Tidy
  2091.                     
  2092.                     def _tidy(data, **kwargs):
  2093.                         (nerrors, nwarnings, data, errordata) = _mxtidy.tidy(data, **kwargs)
  2094.                         return data
  2095.  
  2096.                     break
  2097.             continue
  2098.             continue
  2099.  
  2100.         
  2101.         if _tidy:
  2102.             utf8 = type(data) == type(u'')
  2103.             if utf8:
  2104.                 data = data.encode('utf-8')
  2105.             
  2106.             data = _tidy(data, output_xhtml = 1, numeric_entities = 1, wrap = 0, char_encoding = 'utf8')
  2107.             if utf8:
  2108.                 data = unicode(data, 'utf-8')
  2109.             
  2110.             if data.count('<body'):
  2111.                 data = data.split('<body', 1)[1]
  2112.                 if data.count('>'):
  2113.                     data = data.split('>', 1)[1]
  2114.                 
  2115.             
  2116.             if data.count('</body'):
  2117.                 data = data.split('</body', 1)[0]
  2118.             
  2119.         
  2120.     
  2121.     data = data.strip().replace('\r\n', '\n')
  2122.     return data
  2123.  
  2124.  
  2125. class _FeedURLHandler(urllib2.HTTPDigestAuthHandler, urllib2.HTTPRedirectHandler, urllib2.HTTPDefaultErrorHandler):
  2126.     
  2127.     def http_error_default(self, req, fp, code, msg, headers):
  2128.         if code / 100 == 3 and code != 304:
  2129.             return self.http_error_302(req, fp, code, msg, headers)
  2130.         infourl = urllib.addinfourl(fp, headers, req.get_full_url())
  2131.         infourl.status = code
  2132.         return infourl
  2133.  
  2134.     
  2135.     def http_error_302(self, req, fp, code, msg, headers):
  2136.         if headers.dict.has_key('location'):
  2137.             infourl = urllib2.HTTPRedirectHandler.http_error_302(self, req, fp, code, msg, headers)
  2138.         else:
  2139.             infourl = urllib.addinfourl(fp, headers, req.get_full_url())
  2140.         if not hasattr(infourl, 'status'):
  2141.             infourl.status = code
  2142.         
  2143.         return infourl
  2144.  
  2145.     
  2146.     def http_error_301(self, req, fp, code, msg, headers):
  2147.         if headers.dict.has_key('location'):
  2148.             infourl = urllib2.HTTPRedirectHandler.http_error_301(self, req, fp, code, msg, headers)
  2149.         else:
  2150.             infourl = urllib.addinfourl(fp, headers, req.get_full_url())
  2151.         if not hasattr(infourl, 'status'):
  2152.             infourl.status = code
  2153.         
  2154.         return infourl
  2155.  
  2156.     http_error_300 = http_error_302
  2157.     http_error_303 = http_error_302
  2158.     http_error_307 = http_error_302
  2159.     
  2160.     def http_error_401(self, req, fp, code, msg, headers):
  2161.         host = urlparse.urlparse(req.get_full_url())[1]
  2162.         
  2163.         try:
  2164.             if not sys.version.split()[0] >= '2.3.3':
  2165.                 raise AssertionError
  2166.             if not base64 != None:
  2167.                 raise AssertionError
  2168.             (user, passw) = base64.decodestring(req.headers['Authorization'].split(' ')[1]).split(':')
  2169.             realm = re.findall('realm="([^"]*)"', headers['WWW-Authenticate'])[0]
  2170.             self.add_password(realm, host, user, passw)
  2171.             retry = self.http_error_auth_reqed('www-authenticate', host, req, headers)
  2172.             self.reset_retry_count()
  2173.             return retry
  2174.         except:
  2175.             return self.http_error_default(req, fp, code, msg, headers)
  2176.  
  2177.  
  2178.  
  2179.  
  2180. def _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, handlers):
  2181.     """URL, filename, or string --> stream
  2182.  
  2183.     This function lets you define parsers that take any input source
  2184.     (URL, pathname to local or network file, or actual data as a string)
  2185.     and deal with it in a uniform manner.  Returned object is guaranteed
  2186.     to have all the basic stdio read methods (read, readline, readlines).
  2187.     Just .close() the object when you're done with it.
  2188.  
  2189.     If the etag argument is supplied, it will be used as the value of an
  2190.     If-None-Match request header.
  2191.  
  2192.     If the modified argument is supplied, it must be a tuple of 9 integers
  2193.     as returned by gmtime() in the standard Python time module. This MUST
  2194.     be in GMT (Greenwich Mean Time). The formatted date/time will be used
  2195.     as the value of an If-Modified-Since request header.
  2196.  
  2197.     If the agent argument is supplied, it will be used as the value of a
  2198.     User-Agent request header.
  2199.  
  2200.     If the referrer argument is supplied, it will be used as the value of a
  2201.     Referer[sic] request header.
  2202.  
  2203.     If handlers is supplied, it is a list of handlers used to build a
  2204.     urllib2 opener.
  2205.     """
  2206.     if hasattr(url_file_stream_or_string, 'read'):
  2207.         return url_file_stream_or_string
  2208.     if url_file_stream_or_string == '-':
  2209.         return sys.stdin
  2210.     
  2211.     try:
  2212.         return open(url_file_stream_or_string)
  2213.     except:
  2214.         hasattr(url_file_stream_or_string, 'read') if urlparse.urlparse(url_file_stream_or_string)[0] in ('http', 'https', 'ftp') else url_file_stream_or_string == '-'
  2215.  
  2216.     return _StringIO(str(url_file_stream_or_string))
  2217.  
  2218. _date_handlers = []
  2219.  
  2220. def registerDateHandler(func):
  2221.     '''Register a date handler function (takes string, returns 9-tuple date in GMT)'''
  2222.     _date_handlers.insert(0, func)
  2223.  
  2224. _iso8601_tmpl = [
  2225.     'YYYY-?MM-?DD',
  2226.     'YYYY-MM',
  2227.     'YYYY-?OOO',
  2228.     'YY-?MM-?DD',
  2229.     'YY-?OOO',
  2230.     'YYYY',
  2231.     '-YY-?MM',
  2232.     '-OOO',
  2233.     '-YY',
  2234.     '--MM-?DD',
  2235.     '--MM',
  2236.     '---DD',
  2237.     'CC',
  2238.     '']
  2239. _iso8601_re = [ tmpl.replace('YYYY', '(?P<year>\\d{4})').replace('YY', '(?P<year>\\d\\d)').replace('MM', '(?P<month>[01]\\d)').replace('DD', '(?P<day>[0123]\\d)').replace('OOO', '(?P<ordinal>[0123]\\d\\d)').replace('CC', '(?P<century>\\d\\d$)') + '(T?(?P<hour>\\d{2}):(?P<minute>\\d{2})' + '(:(?P<second>\\d{2}))?' + '(?P<tz>[+-](?P<tzhour>\\d{2})(:(?P<tzmin>\\d{2}))?|Z)?)?' for tmpl in _iso8601_tmpl ]
  2240. del tmpl
  2241. _iso8601_matches = [ re.compile(regex).match for regex in _iso8601_re ]
  2242. del regex
  2243.  
  2244. def _parse_date_iso8601(dateString):
  2245.     '''Parse a variety of ISO-8601-compatible formats like 20040105'''
  2246.     m = None
  2247.     for _iso8601_match in _iso8601_matches:
  2248.         m = _iso8601_match(dateString)
  2249.         if m:
  2250.             break
  2251.             continue
  2252.     
  2253.     if not m:
  2254.         return None
  2255.     if m.span() == (0, 0):
  2256.         return None
  2257.     params = m.groupdict()
  2258.     ordinal = params.get('ordinal', 0)
  2259.     year = params.get('year', '--')
  2260.     if not year or year == '--':
  2261.         year = time.gmtime()[0]
  2262.     elif len(year) == 2:
  2263.         year = 100 * int(time.gmtime()[0] / 100) + int(year)
  2264.     else:
  2265.         year = int(year)
  2266.     month = params.get('month', '-')
  2267.     if not month or month == '-':
  2268.         if ordinal:
  2269.             month = 1
  2270.         else:
  2271.             month = time.gmtime()[1]
  2272.     
  2273.     month = int(month)
  2274.     day = params.get('day', 0)
  2275.     if not day:
  2276.         if ordinal:
  2277.             day = ordinal
  2278.         elif params.get('century', 0) and params.get('year', 0) or params.get('month', 0):
  2279.             day = 1
  2280.         else:
  2281.             day = time.gmtime()[2]
  2282.     else:
  2283.         day = int(day)
  2284.     if 'century' in params.keys():
  2285.         year = (int(params['century']) - 1) * 100 + 1
  2286.     
  2287.     for field in [
  2288.         'hour',
  2289.         'minute',
  2290.         'second',
  2291.         'tzhour',
  2292.         'tzmin']:
  2293.         if not params.get(field, None):
  2294.             params[field] = 0
  2295.             continue
  2296.     
  2297.     hour = int(params.get('hour', 0))
  2298.     minute = int(params.get('minute', 0))
  2299.     second = int(params.get('second', 0))
  2300.     weekday = 0
  2301.     daylight_savings_flag = 0
  2302.     tm = [
  2303.         year,
  2304.         month,
  2305.         day,
  2306.         hour,
  2307.         minute,
  2308.         second,
  2309.         weekday,
  2310.         ordinal,
  2311.         daylight_savings_flag]
  2312.     tz = params.get('tz')
  2313.     if tz and tz != 'Z':
  2314.         if tz[0] == '-':
  2315.             tm[3] += int(params.get('tzhour', 0))
  2316.             tm[4] += int(params.get('tzmin', 0))
  2317.         elif tz[0] == '+':
  2318.             tm[3] -= int(params.get('tzhour', 0))
  2319.             tm[4] -= int(params.get('tzmin', 0))
  2320.         else:
  2321.             return None
  2322.     tz[0] == '-'
  2323.     return time.localtime(time.mktime(tm))
  2324.  
  2325. registerDateHandler(_parse_date_iso8601)
  2326. _korean_year = u'δàä'
  2327. _korean_month = u'∞¢ö'
  2328. _korean_day = u'∞¥╝'
  2329. _korean_am = u'∞ÿñ∞áä'
  2330. _korean_pm = u'∞ÿñφ¢ä'
  2331. _korean_onblog_date_re = re.compile('(\\d{4})%s\\s+(\\d{2})%s\\s+(\\d{2})%s\\s+(\\d{2}):(\\d{2}):(\\d{2})' % (_korean_year, _korean_month, _korean_day))
  2332. _korean_nate_date_re = re.compile(u'(\\d{4})-(\\d{2})-(\\d{2})\\s+(%s|%s)\\s+(\\d{,2}):(\\d{,2}):(\\d{,2})' % (_korean_am, _korean_pm))
  2333.  
  2334. def _parse_date_onblog(dateString):
  2335.     '''Parse a string according to the OnBlog 8-bit date format'''
  2336.     m = _korean_onblog_date_re.match(dateString)
  2337.     if not m:
  2338.         return None
  2339.     w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s:%(second)s%(zonediff)s' % {
  2340.         'year': m.group(1),
  2341.         'month': m.group(2),
  2342.         'day': m.group(3),
  2343.         'hour': m.group(4),
  2344.         'minute': m.group(5),
  2345.         'second': m.group(6),
  2346.         'zonediff': '+09:00' }
  2347.     if _debug:
  2348.         sys.stderr.write('OnBlog date parsed as: %s\n' % w3dtfdate)
  2349.     
  2350.     return _parse_date_w3dtf(w3dtfdate)
  2351.  
  2352. registerDateHandler(_parse_date_onblog)
  2353.  
  2354. def _parse_date_nate(dateString):
  2355.     '''Parse a string according to the Nate 8-bit date format'''
  2356.     m = _korean_nate_date_re.match(dateString)
  2357.     if not m:
  2358.         return None
  2359.     hour = int(m.group(5))
  2360.     ampm = m.group(4)
  2361.     if ampm == _korean_pm:
  2362.         hour += 12
  2363.     
  2364.     hour = str(hour)
  2365.     if len(hour) == 1:
  2366.         hour = '0' + hour
  2367.     
  2368.     w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s:%(second)s%(zonediff)s' % {
  2369.         'year': m.group(1),
  2370.         'month': m.group(2),
  2371.         'day': m.group(3),
  2372.         'hour': hour,
  2373.         'minute': m.group(6),
  2374.         'second': m.group(7),
  2375.         'zonediff': '+09:00' }
  2376.     if _debug:
  2377.         sys.stderr.write('Nate date parsed as: %s\n' % w3dtfdate)
  2378.     
  2379.     return _parse_date_w3dtf(w3dtfdate)
  2380.  
  2381. registerDateHandler(_parse_date_nate)
  2382. _mssql_date_re = re.compile('(\\d{4})-(\\d{2})-(\\d{2})\\s+(\\d{2}):(\\d{2}):(\\d{2})(\\.\\d+)?')
  2383.  
  2384. def _parse_date_mssql(dateString):
  2385.     '''Parse a string according to the MS SQL date format'''
  2386.     m = _mssql_date_re.match(dateString)
  2387.     if not m:
  2388.         return None
  2389.     w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s:%(second)s%(zonediff)s' % {
  2390.         'year': m.group(1),
  2391.         'month': m.group(2),
  2392.         'day': m.group(3),
  2393.         'hour': m.group(4),
  2394.         'minute': m.group(5),
  2395.         'second': m.group(6),
  2396.         'zonediff': '+09:00' }
  2397.     if _debug:
  2398.         sys.stderr.write('MS SQL date parsed as: %s\n' % w3dtfdate)
  2399.     
  2400.     return _parse_date_w3dtf(w3dtfdate)
  2401.  
  2402. registerDateHandler(_parse_date_mssql)
  2403. _greek_months = {
  2404.     u'╬Ö╬▒╬╜': u'Jan',
  2405.     u'╬ª╬╡╬▓': u'Feb',
  2406.     u'╬£╬¼╧Ä': u'Mar',
  2407.     u'╬£╬▒╧Ä': u'Mar',
  2408.     u'╬æ╧Ç╧ü': u'Apr',
  2409.     u'╬£╬¼╬╣': u'May',
  2410.     u'╬£╬▒╧è': u'May',
  2411.     u'╬£╬▒╬╣': u'May',
  2412.     u'╬Ö╬┐╧ì╬╜': u'Jun',
  2413.     u'╬Ö╬┐╬╜': u'Jun',
  2414.     u'╬Ö╬┐╧ì╬╗': u'Jul',
  2415.     u'╬Ö╬┐╬╗': u'Jul',
  2416.     u'╬æ╧ì╬│': u'Aug',
  2417.     u'╬æ╧à╬│': u'Aug',
  2418.     u'╬ú╬╡╧Ç': u'Sep',
  2419.     u'╬ƒ╬║╧ä': u'Oct',
  2420.     u'╬¥╬┐╬¡': u'Nov',
  2421.     u'╬¥╬┐╬╡': u'Nov',
  2422.     u'╬ö╬╡╬║': u'Dec' }
  2423. _greek_wdays = {
  2424.     u'╬Ü╧à╧ü': u'Sun',
  2425.     u'╬ö╬╡╧à': u'Mon',
  2426.     u'╬ñ╧ü╬╣': u'Tue',
  2427.     u'╬ñ╬╡╧ä': u'Wed',
  2428.     u'╬á╬╡╬╝': u'Thu',
  2429.     u'╬á╬▒╧ü': u'Fri',
  2430.     u'╬ú╬▒╬▓': u'Sat' }
  2431. _greek_date_format_re = re.compile(u'([^,]+),\\s+(\\d{2})\\s+([^\\s]+)\\s+(\\d{4})\\s+(\\d{2}):(\\d{2}):(\\d{2})\\s+([^\\s]+)')
  2432.  
  2433. def _parse_date_greek(dateString):
  2434.     '''Parse a string according to a Greek 8-bit date format.'''
  2435.     m = _greek_date_format_re.match(dateString)
  2436.     if not m:
  2437.         return None
  2438.     
  2439.     try:
  2440.         wday = _greek_wdays[m.group(1)]
  2441.         month = _greek_months[m.group(3)]
  2442.     except:
  2443.         m
  2444.         return None
  2445.  
  2446.     rfc822date = '%(wday)s, %(day)s %(month)s %(year)s %(hour)s:%(minute)s:%(second)s %(zonediff)s' % {
  2447.         'wday': wday,
  2448.         'day': m.group(2),
  2449.         'month': month,
  2450.         'year': m.group(4),
  2451.         'hour': m.group(5),
  2452.         'minute': m.group(6),
  2453.         'second': m.group(7),
  2454.         'zonediff': m.group(8) }
  2455.     if _debug:
  2456.         sys.stderr.write('Greek date parsed as: %s\n' % rfc822date)
  2457.     
  2458.     return _parse_date_rfc822(rfc822date)
  2459.  
  2460. registerDateHandler(_parse_date_greek)
  2461. _hungarian_months = {
  2462.     u'janu├ír': u'01',
  2463.     u'febru├íri': u'02',
  2464.     u'm├írcius': u'03',
  2465.     u'├íprilis': u'04',
  2466.     u'm├íujus': u'05',
  2467.     u'j├║nius': u'06',
  2468.     u'j├║lius': u'07',
  2469.     u'augusztus': u'08',
  2470.     u'szeptember': u'09',
  2471.     u'okt├│ber': u'10',
  2472.     u'november': u'11',
  2473.     u'december': u'12' }
  2474. _hungarian_date_format_re = re.compile(u'(\\d{4})-([^-]+)-(\\d{,2})T(\\d{,2}):(\\d{2})((\\+|-)(\\d{,2}:\\d{2}))')
  2475.  
  2476. def _parse_date_hungarian(dateString):
  2477.     '''Parse a string according to a Hungarian 8-bit date format.'''
  2478.     m = _hungarian_date_format_re.match(dateString)
  2479.     if not m:
  2480.         return None
  2481.     
  2482.     try:
  2483.         month = _hungarian_months[m.group(2)]
  2484.         day = m.group(3)
  2485.         if len(day) == 1:
  2486.             day = '0' + day
  2487.         
  2488.         hour = m.group(4)
  2489.         if len(hour) == 1:
  2490.             hour = '0' + hour
  2491.     except:
  2492.         m
  2493.         return None
  2494.  
  2495.     w3dtfdate = '%(year)s-%(month)s-%(day)sT%(hour)s:%(minute)s%(zonediff)s' % {
  2496.         'year': m.group(1),
  2497.         'month': month,
  2498.         'day': day,
  2499.         'hour': hour,
  2500.         'minute': m.group(5),
  2501.         'zonediff': m.group(6) }
  2502.     if _debug:
  2503.         sys.stderr.write('Hungarian date parsed as: %s\n' % w3dtfdate)
  2504.     
  2505.     return _parse_date_w3dtf(w3dtfdate)
  2506.  
  2507. registerDateHandler(_parse_date_hungarian)
  2508.  
  2509. def _parse_date_w3dtf(dateString):
  2510.     
  2511.     def __extract_date(m):
  2512.         year = int(m.group('year'))
  2513.         if year < 100:
  2514.             year = 100 * int(time.gmtime()[0] / 100) + int(year)
  2515.         
  2516.         if year < 1000:
  2517.             return (0, 0, 0)
  2518.         julian = m.group('julian')
  2519.         if julian:
  2520.             julian = int(julian)
  2521.             month = julian / 30 + 1
  2522.             day = julian % 30 + 1
  2523.             jday = None
  2524.             while jday != julian:
  2525.                 t = time.mktime((year, month, day, 0, 0, 0, 0, 0, 0))
  2526.                 jday = time.gmtime(t)[-2]
  2527.                 diff = abs(jday - julian)
  2528.                 if jday > julian:
  2529.                     if diff < day:
  2530.                         day = day - diff
  2531.                     else:
  2532.                         month = month - 1
  2533.                         day = 31
  2534.                 diff < day
  2535.                 if jday < julian:
  2536.                     if day + diff < 28:
  2537.                         day = day + diff
  2538.                     else:
  2539.                         month = month + 1
  2540.                 day + diff < 28
  2541.                 continue
  2542.                 year < 1000
  2543.             return (year, month, day)
  2544.         month = m.group('month')
  2545.         day = 1
  2546.         if month is None:
  2547.             month = 1
  2548.         else:
  2549.             month = int(month)
  2550.             day = m.group('day')
  2551.             if day:
  2552.                 day = int(day)
  2553.             else:
  2554.                 day = 1
  2555.         return (year, month, day)
  2556.  
  2557.     
  2558.     def __extract_time(m):
  2559.         if not m:
  2560.             return (0, 0, 0)
  2561.         hours = m.group('hours')
  2562.         if not hours:
  2563.             return (0, 0, 0)
  2564.         hours = int(hours)
  2565.         minutes = int(m.group('minutes'))
  2566.         seconds = m.group('seconds')
  2567.         return (hours, minutes, seconds)
  2568.  
  2569.     
  2570.     def __extract_tzd(m):
  2571.         '''Return the Time Zone Designator as an offset in seconds from UTC.'''
  2572.         if not m:
  2573.             return 0
  2574.         tzd = m.group('tzd')
  2575.         if not tzd:
  2576.             return 0
  2577.         if tzd == 'Z':
  2578.             return 0
  2579.         hours = int(m.group('tzdhours'))
  2580.         minutes = m.group('tzdminutes')
  2581.         offset = (hours * 60 + minutes) * 60
  2582.         if tzd[0] == '+':
  2583.             return -offset
  2584.         return offset
  2585.  
  2586.     __date_re = '(?P<year>\\d\\d\\d\\d)(?:(?P<dsep>-|)(?:(?P<julian>\\d\\d\\d)|(?P<month>\\d\\d)(?:(?P=dsep)(?P<day>\\d\\d))?))?'
  2587.     __tzd_re = '(?P<tzd>[-+](?P<tzdhours>\\d\\d)(?::?(?P<tzdminutes>\\d\\d))|Z)'
  2588.     __tzd_rx = re.compile(__tzd_re)
  2589.     __time_re = '(?P<hours>\\d\\d)(?P<tsep>:|)(?P<minutes>\\d\\d)(?:(?P=tsep)(?P<seconds>\\d\\d(?:[.,]\\d+)?))?' + __tzd_re
  2590.     __datetime_re = '%s(?:T%s)?' % (__date_re, __time_re)
  2591.     __datetime_rx = re.compile(__datetime_re)
  2592.     m = __datetime_rx.match(dateString)
  2593.     if m is None or m.group() != dateString:
  2594.         return None
  2595.     gmt = __extract_date(m) + __extract_time(m) + (0, 0, 0)
  2596.     if gmt[0] == 0:
  2597.         return None
  2598.     return time.gmtime(time.mktime(gmt) + __extract_tzd(m) - time.timezone)
  2599.  
  2600. registerDateHandler(_parse_date_w3dtf)
  2601.  
  2602. def _parse_date_rfc822(dateString):
  2603.     '''Parse an RFC822, RFC1123, RFC2822, or asctime-style date'''
  2604.     data = dateString.split()
  2605.     if data[0][-1] in (',', '.') or data[0].lower() in rfc822._daynames:
  2606.         del data[0]
  2607.     
  2608.     if len(data) == 4:
  2609.         s = data[3]
  2610.         i = s.find('+')
  2611.         if i > 0:
  2612.             data[3:] = [
  2613.                 s[:i],
  2614.                 s[i + 1:]]
  2615.         else:
  2616.             data.append('')
  2617.         dateString = ' '.join(data)
  2618.     
  2619.     if len(data) < 5:
  2620.         dateString += ' 00:00:00 GMT'
  2621.     
  2622.     tm = rfc822.parsedate_tz(dateString)
  2623.     if tm:
  2624.         return time.gmtime(rfc822.mktime_tz(tm))
  2625.  
  2626. _additional_timezones = {
  2627.     'AT': -400,
  2628.     'ET': -500,
  2629.     'CT': -600,
  2630.     'MT': -700,
  2631.     'PT': -800 }
  2632. rfc822._timezones.update(_additional_timezones)
  2633. registerDateHandler(_parse_date_rfc822)
  2634.  
  2635. def _parse_date(dateString):
  2636.     '''Parses a variety of date formats into a 9-tuple in GMT'''
  2637.     for handler in _date_handlers:
  2638.         
  2639.         try:
  2640.             date9tuple = handler(dateString)
  2641.             if not date9tuple:
  2642.                 continue
  2643.             
  2644.             if len(date9tuple) != 9:
  2645.                 if _debug:
  2646.                     sys.stderr.write('date handler function must return 9-tuple\n')
  2647.                 
  2648.                 raise ValueError
  2649.             len(date9tuple) != 9
  2650.             map(int, date9tuple)
  2651.             return date9tuple
  2652.         continue
  2653.         except Exception:
  2654.             e = None
  2655.             if _debug:
  2656.                 sys.stderr.write('%s raised %s\n' % (handler.__name__, repr(e)))
  2657.             
  2658.             _debug
  2659.         
  2660.  
  2661.     
  2662.  
  2663.  
  2664. def _getCharacterEncoding(http_headers, xml_data):
  2665.     """Get the character encoding of the XML document
  2666.  
  2667.     http_headers is a dictionary
  2668.     xml_data is a raw string (not Unicode)
  2669.     
  2670.     This is so much trickier than it sounds, it's not even funny.
  2671.     According to RFC 3023 ('XML Media Types'), if the HTTP Content-Type
  2672.     is application/xml, application/*+xml,
  2673.     application/xml-external-parsed-entity, or application/xml-dtd,
  2674.     the encoding given in the charset parameter of the HTTP Content-Type
  2675.     takes precedence over the encoding given in the XML prefix within the
  2676.     document, and defaults to 'utf-8' if neither are specified.  But, if
  2677.     the HTTP Content-Type is text/xml, text/*+xml, or
  2678.     text/xml-external-parsed-entity, the encoding given in the XML prefix
  2679.     within the document is ALWAYS IGNORED and only the encoding given in
  2680.     the charset parameter of the HTTP Content-Type header should be
  2681.     respected, and it defaults to 'us-ascii' if not specified.
  2682.  
  2683.     Furthermore, discussion on the atom-syntax mailing list with the
  2684.     author of RFC 3023 leads me to the conclusion that any document
  2685.     served with a Content-Type of text/* and no charset parameter
  2686.     must be treated as us-ascii.  (We now do this.)  And also that it
  2687.     must always be flagged as non-well-formed.  (We now do this too.)
  2688.     
  2689.     If Content-Type is unspecified (input was local file or non-HTTP source)
  2690.     or unrecognized (server just got it totally wrong), then go by the
  2691.     encoding given in the XML prefix of the document and default to
  2692.     'iso-8859-1' as per the HTTP specification (RFC 2616).
  2693.     
  2694.     Then, assuming we didn't find a character encoding in the HTTP headers
  2695.     (and the HTTP Content-type allowed us to look in the body), we need
  2696.     to sniff the first few bytes of the XML data and try to determine
  2697.     whether the encoding is ASCII-compatible.  Section F of the XML
  2698.     specification shows the way here:
  2699.     http://www.w3.org/TR/REC-xml/#sec-guessing-no-ext-info
  2700.  
  2701.     If the sniffed encoding is not ASCII-compatible, we need to make it
  2702.     ASCII compatible so that we can sniff further into the XML declaration
  2703.     to find the encoding attribute, which will tell us the true encoding.
  2704.  
  2705.     Of course, none of this guarantees that we will be able to parse the
  2706.     feed in the declared character encoding (assuming it was declared
  2707.     correctly, which many are not).  CJKCodecs and iconv_codec help a lot;
  2708.     you should definitely install them if you can.
  2709.     http://cjkpython.i18n.org/
  2710.     """
  2711.     
  2712.     def _parseHTTPContentType(content_type):
  2713.         """takes HTTP Content-Type header and returns (content type, charset)
  2714.  
  2715.         If no charset is specified, returns (content type, '')
  2716.         If no content type is specified, returns ('', '')
  2717.         Both return parameters are guaranteed to be lowercase strings
  2718.         """
  2719.         if not content_type:
  2720.             pass
  2721.         content_type = ''
  2722.         (content_type, params) = cgi.parse_header(content_type)
  2723.         return (content_type, params.get('charset', '').replace("'", ''))
  2724.  
  2725.     sniffed_xml_encoding = ''
  2726.     xml_encoding = ''
  2727.     true_encoding = ''
  2728.     (http_content_type, http_encoding) = _parseHTTPContentType(http_headers.get('content-type'))
  2729.     
  2730.     try:
  2731.         if xml_data[:4] == 'Lo\xa7\x94':
  2732.             xml_data = _ebcdic_to_ascii(xml_data)
  2733.         elif xml_data[:4] == '\x00<\x00?':
  2734.             sniffed_xml_encoding = 'utf-16be'
  2735.             xml_data = unicode(xml_data, 'utf-16be').encode('utf-8')
  2736.         elif len(xml_data) >= 4 and xml_data[:2] == '\xfe\xff' and xml_data[2:4] != '\x00\x00':
  2737.             sniffed_xml_encoding = 'utf-16be'
  2738.             xml_data = unicode(xml_data[2:], 'utf-16be').encode('utf-8')
  2739.         elif xml_data[:4] == '<\x00?\x00':
  2740.             sniffed_xml_encoding = 'utf-16le'
  2741.             xml_data = unicode(xml_data, 'utf-16le').encode('utf-8')
  2742.         elif len(xml_data) >= 4 and xml_data[:2] == '\xff\xfe' and xml_data[2:4] != '\x00\x00':
  2743.             sniffed_xml_encoding = 'utf-16le'
  2744.             xml_data = unicode(xml_data[2:], 'utf-16le').encode('utf-8')
  2745.         elif xml_data[:4] == '\x00\x00\x00<':
  2746.             sniffed_xml_encoding = 'utf-32be'
  2747.             xml_data = unicode(xml_data, 'utf-32be').encode('utf-8')
  2748.         elif xml_data[:4] == '<\x00\x00\x00':
  2749.             sniffed_xml_encoding = 'utf-32le'
  2750.             xml_data = unicode(xml_data, 'utf-32le').encode('utf-8')
  2751.         elif xml_data[:4] == '\x00\x00\xfe\xff':
  2752.             sniffed_xml_encoding = 'utf-32be'
  2753.             xml_data = unicode(xml_data[4:], 'utf-32be').encode('utf-8')
  2754.         elif xml_data[:4] == '\xff\xfe\x00\x00':
  2755.             sniffed_xml_encoding = 'utf-32le'
  2756.             xml_data = unicode(xml_data[4:], 'utf-32le').encode('utf-8')
  2757.         elif xml_data[:3] == '\xef\xbb\xbf':
  2758.             sniffed_xml_encoding = 'utf-8'
  2759.             xml_data = unicode(xml_data[3:], 'utf-8').encode('utf-8')
  2760.         
  2761.         xml_encoding_match = re.compile('^<\\?.*encoding=[\'"](.*?)[\'"].*\\?>').match(xml_data)
  2762.     except:
  2763.         xml_encoding_match = None
  2764.  
  2765.     if xml_encoding_match:
  2766.         xml_encoding = xml_encoding_match.groups()[0].lower()
  2767.         if sniffed_xml_encoding and xml_encoding in ('iso-10646-ucs-2', 'ucs-2', 'csunicode', 'iso-10646-ucs-4', 'ucs-4', 'csucs4', 'utf-16', 'utf-32', 'utf_16', 'utf_32', 'utf16', 'u16'):
  2768.             xml_encoding = sniffed_xml_encoding
  2769.         
  2770.     
  2771.     acceptable_content_type = 0
  2772.     application_content_types = ('application/xml', 'application/xml-dtd', 'application/xml-external-parsed-entity')
  2773.     text_content_types = ('text/xml', 'text/xml-external-parsed-entity')
  2774.     if (http_content_type in application_content_types or http_content_type.startswith('application/')) and http_content_type.endswith('+xml'):
  2775.         acceptable_content_type = 1
  2776.         if not http_encoding and xml_encoding:
  2777.             pass
  2778.         true_encoding = 'utf-8'
  2779.     elif (http_content_type in text_content_types or http_content_type.startswith('text/')) and http_content_type.endswith('+xml'):
  2780.         acceptable_content_type = 1
  2781.         if not http_encoding:
  2782.             pass
  2783.         true_encoding = 'us-ascii'
  2784.     elif http_content_type.startswith('text/'):
  2785.         if not http_encoding:
  2786.             pass
  2787.         true_encoding = 'us-ascii'
  2788.     elif http_headers and not http_headers.has_key('content-type'):
  2789.         if not xml_encoding:
  2790.             pass
  2791.         true_encoding = 'iso-8859-1'
  2792.     elif not xml_encoding:
  2793.         pass
  2794.     true_encoding = 'utf-8'
  2795.     return (true_encoding, http_encoding, xml_encoding, sniffed_xml_encoding, acceptable_content_type)
  2796.  
  2797.  
  2798. def _toUTF8(data, encoding):
  2799.     '''Changes an XML data stream on the fly to specify a new encoding
  2800.  
  2801.     data is a raw sequence of bytes (not Unicode) that is presumed to be in %encoding already
  2802.     encoding is a string recognized by encodings.aliases
  2803.     '''
  2804.     if _debug:
  2805.         sys.stderr.write('entering _toUTF8, trying encoding %s\n' % encoding)
  2806.     
  2807.     if len(data) >= 4 and data[:2] == '\xfe\xff' and data[2:4] != '\x00\x00':
  2808.         if _debug:
  2809.             sys.stderr.write('stripping BOM\n')
  2810.             if encoding != 'utf-16be':
  2811.                 sys.stderr.write('trying utf-16be instead\n')
  2812.             
  2813.         
  2814.         encoding = 'utf-16be'
  2815.         data = data[2:]
  2816.     elif len(data) >= 4 and data[:2] == '\xff\xfe' and data[2:4] != '\x00\x00':
  2817.         if _debug:
  2818.             sys.stderr.write('stripping BOM\n')
  2819.             if encoding != 'utf-16le':
  2820.                 sys.stderr.write('trying utf-16le instead\n')
  2821.             
  2822.         
  2823.         encoding = 'utf-16le'
  2824.         data = data[2:]
  2825.     elif data[:3] == '\xef\xbb\xbf':
  2826.         if _debug:
  2827.             sys.stderr.write('stripping BOM\n')
  2828.             if encoding != 'utf-8':
  2829.                 sys.stderr.write('trying utf-8 instead\n')
  2830.             
  2831.         
  2832.         encoding = 'utf-8'
  2833.         data = data[3:]
  2834.     elif data[:4] == '\x00\x00\xfe\xff':
  2835.         if _debug:
  2836.             sys.stderr.write('stripping BOM\n')
  2837.             if encoding != 'utf-32be':
  2838.                 sys.stderr.write('trying utf-32be instead\n')
  2839.             
  2840.         
  2841.         encoding = 'utf-32be'
  2842.         data = data[4:]
  2843.     elif data[:4] == '\xff\xfe\x00\x00':
  2844.         if _debug:
  2845.             sys.stderr.write('stripping BOM\n')
  2846.             if encoding != 'utf-32le':
  2847.                 sys.stderr.write('trying utf-32le instead\n')
  2848.             
  2849.         
  2850.         encoding = 'utf-32le'
  2851.         data = data[4:]
  2852.     
  2853.     newdata = unicode(data, encoding)
  2854.     if _debug:
  2855.         sys.stderr.write('successfully converted %s data to unicode\n' % encoding)
  2856.     
  2857.     declmatch = re.compile('^<\\?xml[^>]*?>')
  2858.     newdecl = "<?xml version='1.0' encoding='utf-8'?>"
  2859.     if declmatch.search(newdata):
  2860.         newdata = declmatch.sub(newdecl, newdata)
  2861.     else:
  2862.         newdata = newdecl + u'\n' + newdata
  2863.     return newdata.encode('utf-8')
  2864.  
  2865.  
  2866. def _stripDoctype(data):
  2867.     """Strips DOCTYPE from XML document, returns (rss_version, stripped_data)
  2868.  
  2869.     rss_version may be 'rss091n' or None
  2870.     stripped_data is the same XML document, minus the DOCTYPE
  2871.     """
  2872.     entity_pattern = re.compile('<!ENTITY([^>]*?)>', re.MULTILINE)
  2873.     data = entity_pattern.sub('', data)
  2874.     doctype_pattern = re.compile('<!DOCTYPE([^>]*?)>', re.MULTILINE)
  2875.     doctype_results = doctype_pattern.findall(data)
  2876.     if not doctype_results or doctype_results[0]:
  2877.         pass
  2878.     doctype = ''
  2879.     if doctype.lower().count('netscape'):
  2880.         version = 'rss091n'
  2881.     else:
  2882.         version = None
  2883.     data = doctype_pattern.sub('', data)
  2884.     return (version, data)
  2885.  
  2886.  
  2887. def parse(url_file_stream_or_string, etag = None, modified = None, agent = None, referrer = None, handlers = []):
  2888.     '''Parse a feed from a URL, file, stream, or string'''
  2889.     result = FeedParserDict()
  2890.     result['feed'] = FeedParserDict()
  2891.     result['entries'] = []
  2892.     if _XML_AVAILABLE:
  2893.         result['bozo'] = 0
  2894.     
  2895.     if type(handlers) == types.InstanceType:
  2896.         handlers = [
  2897.             handlers]
  2898.     
  2899.     
  2900.     try:
  2901.         f = _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, handlers)
  2902.         data = f.read()
  2903.     except Exception:
  2904.         e = None
  2905.         result['bozo'] = 1
  2906.         result['bozo_exception'] = e
  2907.         data = ''
  2908.         f = None
  2909.  
  2910.     if f and data and hasattr(f, 'headers'):
  2911.         if gzip and f.headers.get('content-encoding', '') == 'gzip':
  2912.             
  2913.             try:
  2914.                 data = gzip.GzipFile(fileobj = _StringIO(data)).read()
  2915.             except Exception:
  2916.                 e = None
  2917.                 result['bozo'] = 1
  2918.                 result['bozo_exception'] = e
  2919.                 data = ''
  2920.             except:
  2921.                 None<EXCEPTION MATCH>Exception
  2922.             
  2923.  
  2924.         None<EXCEPTION MATCH>Exception
  2925.         if zlib and f.headers.get('content-encoding', '') == 'deflate':
  2926.             
  2927.             try:
  2928.                 data = zlib.decompress(data, -(zlib.MAX_WBITS))
  2929.             except Exception:
  2930.                 e = None
  2931.                 result['bozo'] = 1
  2932.                 result['bozo_exception'] = e
  2933.                 data = ''
  2934.             except:
  2935.                 None<EXCEPTION MATCH>Exception
  2936.             
  2937.  
  2938.         None<EXCEPTION MATCH>Exception
  2939.     
  2940.     if hasattr(f, 'info'):
  2941.         info = f.info()
  2942.         if info.has_key('Etag'):
  2943.             result['etag'] = info.getheader('ETag')
  2944.         
  2945.         last_modified = info.getheader('Last-Modified')
  2946.         if last_modified:
  2947.             result['modified'] = _parse_date(last_modified)
  2948.         
  2949.     
  2950.     if hasattr(f, 'url'):
  2951.         result['href'] = f.url
  2952.         result['status'] = 200
  2953.     
  2954.     if hasattr(f, 'status'):
  2955.         result['status'] = f.status
  2956.     
  2957.     if hasattr(f, 'headers'):
  2958.         result['headers'] = f.headers.dict
  2959.     
  2960.     if hasattr(f, 'close'):
  2961.         f.close()
  2962.     
  2963.     http_headers = result.get('headers', { })
  2964.     (result['encoding'], http_encoding, xml_encoding, sniffed_xml_encoding, acceptable_content_type) = _getCharacterEncoding(http_headers, data)
  2965.     if http_headers and not acceptable_content_type:
  2966.         if http_headers.has_key('content-type'):
  2967.             bozo_message = '%s is not an XML media type' % http_headers['content-type']
  2968.         else:
  2969.             bozo_message = 'no Content-type specified'
  2970.         result['bozo'] = 1
  2971.         result['bozo_exception'] = NonXMLContentType(bozo_message)
  2972.     
  2973.     (result['version'], data) = _stripDoctype(data)
  2974.     baseuri = http_headers.get('content-location', result.get('href'))
  2975.     baselang = http_headers.get('content-language', None)
  2976.     if result.get('status', 0) == 304:
  2977.         result['version'] = ''
  2978.         result['debug_message'] = 'The feed has not changed since you last checked, ' + 'so the server sent no data.  This is a feature, not a bug!'
  2979.         return result
  2980.     if not data:
  2981.         return result
  2982.     use_strict_parser = 0
  2983.     known_encoding = 0
  2984.     tried_encodings = []
  2985.     for proposed_encoding in (result['encoding'], xml_encoding, sniffed_xml_encoding):
  2986.         if proposed_encoding in tried_encodings:
  2987.             continue
  2988.         
  2989.         tried_encodings.append(proposed_encoding)
  2990.         
  2991.         try:
  2992.             data = _toUTF8(data, proposed_encoding)
  2993.             known_encoding = use_strict_parser = 1
  2994.         continue
  2995.         continue
  2996.  
  2997.     
  2998.     if not known_encoding and chardet:
  2999.         
  3000.         try:
  3001.             proposed_encoding = chardet.detect(data)['encoding']
  3002.             if proposed_encoding and proposed_encoding not in tried_encodings:
  3003.                 tried_encodings.append(proposed_encoding)
  3004.                 data = _toUTF8(data, proposed_encoding)
  3005.                 known_encoding = use_strict_parser = 1
  3006.  
  3007.     
  3008.     if not known_encoding and 'utf-8' not in tried_encodings:
  3009.         
  3010.         try:
  3011.             proposed_encoding = 'utf-8'
  3012.             tried_encodings.append(proposed_encoding)
  3013.             data = _toUTF8(data, proposed_encoding)
  3014.             known_encoding = use_strict_parser = 1
  3015.  
  3016.     
  3017.     if not known_encoding and 'windows-1252' not in tried_encodings:
  3018.         
  3019.         try:
  3020.             proposed_encoding = 'windows-1252'
  3021.             tried_encodings.append(proposed_encoding)
  3022.             data = _toUTF8(data, proposed_encoding)
  3023.             known_encoding = use_strict_parser = 1
  3024.  
  3025.     
  3026.     if not known_encoding:
  3027.         result['bozo'] = 1
  3028.         result['bozo_exception'] = CharacterEncodingUnknown('document encoding unknown, I tried ' + '%s, %s, utf-8, and windows-1252 but nothing worked' % (result['encoding'], xml_encoding))
  3029.         result['encoding'] = ''
  3030.     elif proposed_encoding != result['encoding']:
  3031.         result['bozo'] = 1
  3032.         result['bozo_exception'] = CharacterEncodingOverride('documented declared as %s, but parsed as %s' % (result['encoding'], proposed_encoding))
  3033.         result['encoding'] = proposed_encoding
  3034.     
  3035.     if not _XML_AVAILABLE:
  3036.         use_strict_parser = 0
  3037.     
  3038.     if use_strict_parser:
  3039.         feedparser = _StrictFeedParser(baseuri, baselang, 'utf-8')
  3040.         saxparser = xml.sax.make_parser(PREFERRED_XML_PARSERS)
  3041.         saxparser.setFeature(xml.sax.handler.feature_namespaces, 1)
  3042.         saxparser.setContentHandler(feedparser)
  3043.         saxparser.setErrorHandler(feedparser)
  3044.         source = xml.sax.xmlreader.InputSource()
  3045.         source.setByteStream(_StringIO(data))
  3046.         if hasattr(saxparser, '_ns_stack'):
  3047.             saxparser._ns_stack.append({
  3048.                 'http://www.w3.org/XML/1998/namespace': 'xml' })
  3049.         
  3050.         
  3051.         try:
  3052.             saxparser.parse(source)
  3053.         except Exception:
  3054.             e = None
  3055.             if _debug:
  3056.                 import traceback
  3057.                 traceback.print_stack()
  3058.                 traceback.print_exc()
  3059.                 sys.stderr.write('xml parsing failed\n')
  3060.             
  3061.             result['bozo'] = 1
  3062.             if not feedparser.exc:
  3063.                 pass
  3064.             result['bozo_exception'] = e
  3065.             use_strict_parser = 0
  3066.         except:
  3067.             None<EXCEPTION MATCH>Exception
  3068.         
  3069.  
  3070.     None<EXCEPTION MATCH>Exception
  3071.     if not use_strict_parser:
  3072.         if not known_encoding or 'utf-8':
  3073.             pass
  3074.         feedparser = _LooseFeedParser(baseuri, baselang, '')
  3075.         feedparser.feed(data)
  3076.     
  3077.     result['feed'] = feedparser.feeddata
  3078.     result['entries'] = feedparser.entries
  3079.     if not result['version']:
  3080.         pass
  3081.     result['version'] = feedparser.version
  3082.     result['namespaces'] = feedparser.namespacesInUse
  3083.     return result
  3084.  
  3085. if __name__ == '__main__':
  3086.     zopeCompatibilityHack()
  3087.     from pprint import pprint
  3088.     for url in urls:
  3089.         print url
  3090.         print 
  3091.         result = parse(url)
  3092.         pprint(result)
  3093.         print 
  3094.     
  3095.  
  3096.